Erik's Security Blog Practicing Constant Vigilance

SMTChecker: Validating solidity code

SMTChecker is a powerful built-in solc feature. SMTChecker enables formal verification of solidity code directly using the solidity contracts. While this feature is unused and underappreciate today, I hope this post may help change that.

Installation

There are no step-by-step installation instructions in the solidity docs for SMTChecker, which would be nice to change in the future. I found two easy ways to use SMTChecker:

  1. Use remix, which has SMTChecker built-in to the WebAssembly binary. The line pragma experimental SMTChecker; must be added in remix to activate SMTChecker.
  2. Install a dynamic (not static) z3 library on a *nix system (you can use docker or a VM), which the *nix solc binary on the releases page will automatically use. To install the dynamic z3 library on Ubuntu, you can use sudo apt install libz3-4 libz3-dev, which should add a libz3.so file to one of your default library paths such as /usr/lib.

If SMTChecker cannot find z3 on your system, it should return a warning message such as Warning: z3 was selected as a Horn solver for CHC analysis but libz3.so.4.8 was not found. .

Shortcomings

Before moving further, be aware that SMTChecker does not support assembly code. SMTChecker currently ignores assembly code. If you want a comparison of some other similar tools, this medium post and this stack exchange answer may be useful.

Using SMTChecker

The are many options and arguments that can be used to customize how SMTChecker runs. These options are clearly labeled in the output of solc --help. Most options have sensible defaults that I leave as they are, but the options I use most often are:

  1. --model-checker-engine: without this option, the SMTChecker will not run and will not catch any assertion violations. This option can be set to all, bmc, or chc. I normally use chc or all.
  2. --model-checker-timeout: setting this option to zero will remove resource restrictions for the SMTChecker. If this option is not used, the SMTChecker may stop running before it finishes, resulting in incomplete outputs. I normally set this to zero to remove any limits.
  3. --model-checker-show-unproved: The SMTChecker will sometimes output a message suggesting to add this option because it did not finish proving all invariants. I sometimes add this option, but because the SMTChecker generally suggestions when adding it is useful, I don’t always add this option.

Unusual error

I encountered an unusual (and dumb) error with SMTChecker that I will describe in case it saves someone some troubleshooting. I thought SMTChecker was not detecting z3 because the output from running SMTChecker had many blank lines with only a few words like “Warning” and one line of code showing in the output. The issue was that the output from SMTChecker was actually correct, but the terminal font colors I was using rendered most of the output invisible. It was only by copying the output (with “blank” lines included) into an empty text file that I detected the issue. Lesson learned, blank lines aren’t always blank.

SMTChecker output with "blank" lines

Concluding Remarks

VS Code Extensions: gaining x-ray vision

Visual Studio Code, better known as VS Code, has turned into the default editor for blockchain devs and auditors. One of the reasons for its popularity is the variety of useful extensions that add features for solidity and vyper files. The features of VS Code extensions that I have installed are examined below, though your specific use cases may require more or fewer extensions than those listed.

Installing VS Code

VS Code is a cross-platform IDE and the installation process is straightforward. Follow the instructions on the official site.

Intro to VS Code

With VS Code installed, try opening up a local Ethereum project on your system. Navigate to File -> Open Folder and open the top level directory of your project, where your package.json or other config files are located. With the proper folder opened, you can navigate through the file structure of your project directory using the so-called Activity Bar, seen in the screenshot below.

VS Code Explorer menu

Further down the Activity Bar you will find an Extensions button, which is the easiest place to install extensions from. Alternatively, you can install extensions using the official Visual Studio Marketplace website.

VS Code Extensions menu

Solidity Visual Developer

This extension is very popular and easy to identify in screenshots or videos by the distinctive syntax highlighting. The syntax highlighting makes it easier to identify whether a variable is a function argument, state variable, or a temporary variable, which is useful when reading code.

Solidity Visual Developer syntax highlighting

Another useful feature is found when you scroll to the top of a Solidity file. You will see buttons for different CodeLenses, which provide different views for the code you are reviewing. My favorite is the function call graph, automatically generated by pressing the “graph (this)” CodeLens button, which is a visual representation of how functions are connected in a contract.

Solidity Visual Developer function call graph

The “report” CodeLens button uses Surya to generate a report. The report is in Markdown, so to make the report more legible I like to render the markdown with VS Code’s Ctrl+Shift+V shortcut. Personally I find this report less useful than the one displayed later in this post, but if you prefer visuals, you may find the report useful.

Solidity Visual Developer report

The other CodeLens buttons generate interesting visual as well, but I don’t use them often. The additional features of this extension are well documented on the extension’s marketplace page.

Inline Bookmarks

This extension is very helpful for adding notes during an audit of a code base. This extension adds just one feature, but a useful one. By adding comments in solidity files with special tags, such as @note, @todo, @audit, and @audit-ok, you can create bookmarks in the code that are indexed and easily referenced. The automatically updates bookmarks index is found in the Activity Bar on the left side in a special section of the Explorer labeled Inline Bookmarks. This makes it easy to find notes to yourself that are written inline during a code review.

Inline Bookmarks

Solidity Metrics

This extension provides a nicely formatted automated report with some sections that I find useful. To generate the report, navigate to the Activity Bar and right click on your project’s contracts directory. The last item in the pop-up menu should be “Solidity: Metrics”, which will generate a report for all solidity files in that directory.

Solidity Metrics generate report

The report sections I find most useful are:

  1. The “Source Units in Scope” table lists the lines of code for each contract and provides visual icons for the capabilities of each contract.
  2. The “Risk” section provides an algorithmic score the different categories of concern that may elevate the contract’s risk. Because this is automatically generated, it’s not always accurate, but can give a ballpark idea of how challenging the code review may be.
  3. The “Capabilities” section mentions whether common high-risk functions or capabilities were found in the project. These deserve closer attention if they exist in the contracts.
  4. The “Dependencies / External Imports” gives a quick view of the external contracts that might be inherited. Outdated dependencies or poor integration with inherited contracts can be a security finding.

Solidity Metrics lines of code

Solidity Metrics lines of code

Because this Solidity Metrics report includes the same Surya report from the Solidity Visual Developer CodeLens (it’s at the very end of the Solidity Metrics report), I think this is the best autogenerated solidity report I’ve seen. For more details about this extension, see this video with a walkthrough of the extension from the Consensys Diligence team.

Vyper

This extension adds vyper syntax highlighting. It’s useful to have installed and enabled for when you encounter vyper code.

Ethereum Security Bundle

This extension is a meta-extension that has many other extensions as dependencies to allow easy batch installation. It does not add any custom functionality of its own, but provides a fast way to set up a new environment. The extensions that it installs includes most extensions mentioned above, plus some others.

ETHover

This extension is one I haven’t used that much. It provides an easy way to download data from etherscan, so it would be most useful when reviewing code that references mainnet addresses.

Solidity

This extension is another popular one and has some overlap (syntax highlighting for one) with the Solidity Visual Developer extension. Personally, I had this extension conflicting with another one (or maybe the other extension conflicted with this one, I never figured out the issue) so I have this extension installed but disabled. The simple built-in linting is one feature I don’t think I have in my other installed extensions.

Others

I use some other extensions that are not specific to Ethereum code, but are still helpful.

Code Spell Checker

This extension is a simple spell checker that is helpful when writing reports, comments, or code in VS Code. It can even point out typos in code that you are reviewing.

Markdown All in One

This extension is helpful when dealing with markdown files. It has many features, but the ones I use most are the keyboard shortcuts for markdown preview, “Create Table of Contents”, auto completion, and URL link pasting.

Concluding Remarks

VS Code has become very popular in the ecosystem and has good extensions to support Ethereum developers and auditors. In fact, if you want to avoid the default Etherscan contract code viewer, you can use Dethcode to view Etherscan contract code in your favorite familiar VS Code layout. A round of applause to tintinweb at Consensys who authored many of the Solidity extension above. Each extension has its own use cases, and while most will not catch bugs in the code immediately, they make it easier to review code and find bugs.

Using Slither for Smart Contract Security Testing

If you’ve spent any amount of time researching solidity security or doing security reviews on smart contract code, you’ve heard of or used the tool Slither from Trail of Bits. Most Slither tutorials I’ve seen don’t provide step-by-step instructions for using the tool from a security auditor’s perspective. Most tutorials only cover the basics of using Slither. But this tutorial attempts to provide a more in-depth view of how Slither fits into my security testing workflow.

Why Slither

Slither is a static analysis tool that often requires no setup on the security tester’s part. If the code compiles with hardhat or several other frameworks, Slither can be used. The lack of any setup process is a key part of why Slither has become so widely used. Fuzzing tools are great, but setup effort is required, so static analysis tools like Slither keep life simple. Unlike other solidity static analysis tools, Slither is regularly updated and maintained, which has played a large part in why it has superceded most (all?) other solidity static analysis tools at the time of writing.

Installation and First Steps

The first step to using Slither is to install Slither. There are several installation options, and using a Python virtual environment is a good practice, but the lazy way of installing with pip is to use pip3 install slither-analyzer.

Next, make sure the solidity version you have installed is the same one required by the contracts. Compare the solc version you have installed to the pragma version in the .sol contract files you want to test. If you don’t have a project that you want to analyze of your own, you can follow along using a well-known public project.

solc --version # this prints the solc version
grep -inr pragma  # this prints the solidity version in your contracts

If the solc version doesn’t match the version number of the contracts, you can get the correct solc version either with the solc-select tool, by keeping separate Docker images for different solidity versions like Scott Bigelow describes here, or by downloading the proper version of solc binary from the solidity github repository and copying it over your existing solc binary using:

# Add `sudo` to this command if it doesn't work without sudo
cp solc-static-linux $(which solc)

To confirm Slither is installed, print the Slither help message with slither -h. If the output shows a long list of Slither command options (as opposed to an error), Slither should be installed properly. The latest version of Slither at the time of writing was 0.8.2.

Compiling the Contracts

Now that you have Slither installed and solc is the correct version, you need to make sure you can compile the contracts without any errors. Normally a project will require the installation of JavaScript dependencies before it can be built. These dependencies can be installed with either npm or yarn. The best approach is to read the project documentation and use the build instructions provided. If there are no instructions, I use a simple rule of thumb. If there is a yarn.lock file in the project directory, use yarn install to install the dependencies. If there is only a package.json file but no yarn.lock file, then use npm install to install the dependencies.

The command used for compiling the project depends on the development toolchain used. If hardhat is used, you should find a hardhat.config.ts or hardhat.config.js file in the top level directory. Make sure you are in the same directory as the hardhat.config.js file when you run npx hardhat compile.

If another development toolchain is used, such as truffle, waffle, dapptools, foundry, etc., refer to the project’s documentation or visit the official website for the proper development toolchain and read the toolchain documentation on how to compile a project with that tool. If brownie or foundry are the build frameworks, you’re out of luck as of early 2022 - see the upcoming section about that.

Running Slither

After you have the project compiling without errors using its native framework, it’s time to run Slither. The easiest way to run all 76 Slither detectors is by navigating to the top level directory of your project and running slither .. If all goes well, you should see colorful output like the screenshot below.

Slither output

Many examples online have more arguments in the command, but you don’t need them for Slither to run successfully. There are many old issues in the Slither repository where users try running Slither with slither ./contracts, but this is incorrect because Slither expects to receive the top level directory of the project, not the contracts directory. By default, Slither will compile the contracts when it is run. If you just finished compiling your contract with npx hardhat compile or a similar command, there is no need for Slither to waste time repeating the compilation, so you can run Slither with slither . --hardhat-ignore-compile to tell Slither to skip compiling the code and save some time. The slither -h help message shows there is a similar flag to skip compilation with waffle, dapptools, etc.

Troubleshooting Slither Compilation Errors

Slither will sometimes encounter an error. If you are testing a project that uses brownie or foundry, skip to the end to this section for an explanation on known open issues. Sometimes reading the Slither error message will guide you on how to solve the error, but sometimes it’s not very helpful. In these cases, I start by checking whether the program compiles by testing:

  1. Can I compile the project with the native framework? For example, if the project uses hardhat, try npx hardhat compile. If this fails, you might have an error in your code or are missing dependencies.
  2. If the native framework compilation succeeds, does the project compile with crytic-compile? Run crytic-compile . If this fails, read the error messages to see if they guide you to a solution, or you might be using brownie or foundry and are straight out of luck.
  3. Finally, if crytic-compile succeeds, run slither . or slither . --hardhat-ignore-compile (or the corresponding ignore-compile flag for your framework). Slither might not recognize the proper build framework, in which case you can use the --compile-force-framework <framework> option to force a specific framework.

For some deeper background, crytic-compile is what Slither uses to compile the contracts before it begins static analysis, and sometimes crytic-compile doesn’t like the code you try to compile. If the three steps above don’t solve the issue for me, I take some keywords from the error messages I received from Slither and crytic-compile and search for Slither past issues and crytic-compile past issues. It can help to search both projects because sometimes the issue can get posted to the wrong project. If your searches don’t yield fruit, and searching on Google and Stack Overflow doesn’t help either, it might be time to post a new issue to the Slither GitHub.

Other Slither Troubleshooting

Other edge cases I have encountered include:

  1. The error message is related to missing fields in the “networks” section of hardhat.config.ts, which might be due to a missing .env file. If your goal is only to run Slither, you can delete the entire “networks” section of hardhat.config.ts because it’s only used for deployment tests and unit tests.
  2. Sometimes the package.json file doesn’t include all the dependencies for a project. You can install a single npm dependency individually for the local project, without modifying the package.json file, using npm install <package-name>. For example, npm install @rari-capital/solmate. You can use npm install with a GitHub URL like npm install https://github.com/OpenZeppelin/openzeppelin-contracts.
  3. Test files sometimes interfere with Slither, so deleting the contracts/tests directory, if there is one, can help.
  4. Occasionally Slither doesn’t like how the imports are written in the contract files. You can use the --solc-remaps command line flag to fix this. For example, to tell Slither that the “@openzeppelin/contracts” path in an import statement actually means “./build/dependencies”, use slither . --solc-remaps @openzeppelin/contracts=./build/dependencies.

Using a Custom Slither Config File

If you’ve used Slither before, you may have been annoyed by findings concerning dependencies in the node_modules folder. A Slither config file can simplify your output and remove these findings. The full list of config file options is on the project page, but I normally filter out the node_modules folder at a minimum. This config file can have any name, but Slither will require the --config-file NewFileName argument to specify where the config file is if the default name is not used. For this reason, I find it easier to always use the default config file name, which is “slither.config.json”. Copy the filter_paths statement below into a file named “slither.config.json” located in the top level directory of your project (the same location where you run slither .) and Slither will use this config file for your project.

{
    "filter_paths": "node_modules"
}

The config file data above is will remove findings from the node_modules directory. You can go further and remove multiple paths from Slither results. For example, if you wanted to remove the “contract/Interfaces” and the “contract/Math” directories from Slither analysis, you could use the following config file.

{
    "filter_paths": "node_modules|Interfaces|Math"
}

Be aware that Slither documentation as of April 2022 suggests separating filter path values with a , instead of a |, which is incorrect.

Another useful config file option is to filter out findings of a certain risk level. For example, if we wanted to remove optimization, informational, and low risk findings, which often have some false positives that clutter the results output, we could use this config file.

{
    "filter_paths": "node_modules",
    "exclude_optimization": true,
    "exclude_informational": true,
    "exclude_low": true
}

Config File Alternative: Command Flags

The output of slither --help lists the optional flags you can add to the slither . command. Adding flags will give the same results as a config file. I prefer storing the config data in a config file, but that is my personal choice. For example, to duplicate the config file result above, you can use slither . --filter-paths "node_modules" --exclude-optimization --exclude-informational --exclude-low.

Bonus Feature: Slither Github Action

In February 2022, the Slither GitHub action was published. This can run Slither automatically each time a PR or commit is added to your project. Whether you would find Slither output after each commit to be useful is your choice, but filtering out the low risk issues using the config file above may help reduce the noise in the results.

Some Slither Weaknesses

There’s a few quirks with Slither that are minor and understandable for an automated tool with no setup required. It is worth mentioning these in case you encounter the same quirks.

The first quirk is that Slither doesn’t consider the modifiers involved when checking for findings like reentrancy. For example, Slither might identify a function as vulnerable to reentrancy even though there is a “nonReentrant” or “lock” modifier on the function. In other places, it might flag a function such as “selfdestruct” when the “onlyOnly” modifier exists on the function. It is always mandatory to check the output of any automated security tool, but I have learned to expect to see “nonReentrant” modifiers on most functions that Slither highlights as potentially vulnerable to reentrancy.

There is still plenty of room to grow for automated solidity security tools of the future. Several detectors could be added to handle Open Zeppelin dependencies alone to make sure they are used properly, let along other libraries. Possible checks could include: 1. checking for an initializer modifier on any function containing the letters “initialize” or “initialization” to prevent the possibility of multiple initializations 2. validating that inherited contracts are initialized properly if openzeppelin-contracts-upgradeable imports exist 3. checking the package.json for outdated versions of Open Zeppelin contracts with known security issues.

Slither Printers

I first saw the Slither printer feature demoed in this talk by Scott Bigelow. This feature is well documented. Printers are a way to visualize the functions in the project in different ways. There are 18 different printers, each providing a different view on the project code. To list the different printers, use slither --list-printers. Here are some examples of using the printers, and remember that adding the --hardhat-ignore-compile argument can speed up the process by skipping compilation:

  • slither . --print human-summary provides a summary report of the Slither static analyzer output, along with extra information
  • slither . --print function-summary provides a summary of all functions with their corresponding modifiers, internal calls, state variable read/write, etc.
  • slither . --print variable-order lists the storage slots used by each variable
  • slither . --print vars-and-auth lists the state variables modified in each function and the authorization applied to msg.sender
  • slither . --print constructor-calls prints the constructor function contents for each contract and its inherited contracts

The main difficulty I have had using these printers is sizing the output properly so the table renders neatly while the text is still large enough to be legible. I personally prefer the layout provided by VS Code extensions that serve a similar purpose.

Slither human-summary printer output

Slither function-summary printer output

Other Slither Tools

Slither’s static analyzer may be the most commonly used part of Slither, but wait, there’s more! Slither also includes the following CLI tools:

  • slither-check-erc
  • slither-check-kspec
  • slither-check-upgradeability
  • slither-find-paths
  • slither-flat
  • slither-format
  • slither-mutate
  • slither-prop
  • slither-simil

Not all of these tools have much official documentation. I will only mention a few here.

Slither ERC Checker

The slither-check-erc tool is the tool I have used the most, besides the main static analyzer. Any time I encounter a contract that implements a common ERC token, I run this tool as a sanity check. If you’re really lazy, check the output of slither . --print human-summary and look for the ERCs output line to see what is automatically detected. The output of slither-check-erc lists whether all the functions required by the ERC specification are implemented. The output contains optional functions for the ERC too, pointing out possible areas for improvement even if the minimum requirements are met. Using slither-check-erc is very similar to running Slither, but the contract name must be provided as an input argument. The command to run this tool on an ERC20 token contract, which is the default ERC for the tool, is shown below.

slither-check-erc . ERC20ContractName

slither-check-erc supports the following tokens: ERC20,ERC223,ERC165,ERC721,ERC1820,ERC777,ERC1155. To test a token other than ERC20, include the --erc argument.

slither-check-erc . --erc ERC1155 yAcadToken1155

Slither Flattener

The slither-flat tool flattens a codebase. What this means in practice is that all contracts for a project are reorganized into a single folder. This process is most useful to prepare the contracts for another tool like mythx that might encounter problems with external contract imports. You can see the results yourself using slither-flat ., which will store the output files in a new directory named “crytic-export/flattening”.

Slither Upgradeability Checker

The slither-check-upgradeability tool automatically checks contracts that use the delegatecall proxy pattern. Because of the evolving nature of proxy architecture and strategies, I haven’t used this tool as much as expected, but if you do encounter this scenario, the tool is definitely worth using. It can be run with slither-check-upgradeability ContractName.sol ContractName.

Writing Custom Python Code with Slither

You can use Slither’s intermediate representation to write your own Python scripts.

Here’s one custom code example that prints out the contract name and function name if a function match the criteria 1. the function is public or external 2. state variables are modified in the function 3. there is no onlyOwner modifier on the function. Save this code in a Python .py file in the same top level directory where you run slither .. This python script is run like any other python script, using python3 filename.py.

from slither.slither import Slither  
  
slither = Slither('.')

for contract in slither.contracts:
    for function in contract.functions:
        if function.is_constructor:
            continue
        if function.visibility in ['public', 'external']:
            if len(function.state_variables_written) > 0:
                if not 'onlyOwner()' in [m.full_name for m in function.modifiers]:
                    print('Contract: '+ contract.name)
                    print('>>Function: {}'.format(function.name) + ' is unprotected!')

What to do with Brownie and Foundry Projects?

There are open issues that brownie and foundry don’t currently play nice with Slither (technically the issue is with crytic-compile, which Slither relies upon), so what is the workaround? My simple hack is to create a new hardhat project, copy the contracts over, fix dependencies as needed, make sure the code compiles, and run Slither on the hardhat project. Here are my steps to doing this.

First, check that you have hardhat installed globally on your system by viewing the help message from npx hardhat --help. If you see the hardhat help message, then setting up a fresh hardhat project is as simple as running npx hardhat in the directory where you want to store the project, selecting the “Create a basic sample project” option when asked, and answering the prompts (usually by tapping enter to accept the defaults until no more prompts appear).

A few final steps are needed to copy over the original project to the blank hardhat template.

  1. Delete the “contracts” directory from the sample hardhat template, because it contains a default Greeter.sol file that we don’t need. Then copy the “contracts” directory from the original project to the new sample project.
  2. Open the original package.json file and copy the “scripts” and “dependencies” portions of the original package.json file to the new package.json file in the sample hardhat project.
  3. In the new sample hardhat project hardhat.config.js file, set the proper solidity version based on the version used in the .sol files in the “contracts” directory.

If you’re lucky, npx hardhat compile should compile the project normally now. If you’re not lucky, the Slither error messages can often guide you in the right direction, especially if you’re willing to tinker around with the config files and/or imports to get everything working properly. Check out the troubleshooting section for more ideas. If the only goal is to run Slither, I don’t worry about copying the tests or other files because Slither only cares about the files in the contracts directory.

Seth: A Blockchain CLI Tool

Seth is a very useful command line interface (CLI) tool from DappTools that is useful for poking around the blockchain. Seth pulls a lot of the same blockchain data that Etherscan does, and a few small pieces of data come from the Etherscan API. But the CLI tool format makes it easy to include seth for scripting and automation purposes. Let’s see what it can do.

Installing Seth

To use seth, you need to install dapptools. The official instructions using Nix are quite straightforward, so I defer to them. The other important setup step is to set up the “ETH_RPC_URL” environment variable with an RPC endpoint. You can create a free account with Alchemy or Infura to get your own RPC endpoint URL.

Fetching Contract Source Code

Seth connects to the Etherscan API to provide a simple way to download contract source code. If you prefer reading code in your local editor like VS Code instead of reading from etherscan or etherscan.deth.net, this one’s for you. First make sure you have a free Etherscan account so you can get an Etherscan API key. Then store the Etherscan API key in your ETHERSCAN_API_KEY environment variable, because this variable name is where seth is configured to look for the key. Afterwards, you can download any contract source code if the code is verified on Etherscan. For example, to download the WETH contract source code, use the following (and wait a bit if the Etherscan API is slow):

seth etherscan-source 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 | jq .SourceCode -r

To show the potential of packaging this single feature in a CLI, you could use this feature to build a bug bounty automation platform. Although such a system is common for web2 bug bounties, no public example of this type of project for web3 has been published yet. If you want to start building, the system could consist of:

  1. A project like changedetection.io to monitor the list of bug bounty target contracts from Immunefi, Hackenproof, Hats.finance, or other another website
  2. When a change is detected, either when a new project is added or when a new contract is added to the scope of an existing project, download the source code of the contract using seth
  3. Run your code analysis process on the new contract source code (hint: this step is the hard part of the whole process)
  4. Receive automated alerts from your system when it has found something (for an easy option, consider the poor man’s monitoring setup)
  5. Submit findings to bounty platforms & profit

Zooming In On The Latest Block

We can analyze individual blocks with the seth block <block number> command and the most recent block number is queried with seth block-number. We can chain these two commands together and receive data about the most recent block with seth block $(seth block-number), which gives output like:

seth block data

baseFeePerGas           28841430027
difficulty              13016799566303853
extraData               1936027442
gasLimit                30000000
gasUsed                 8255948
hash                    0xb22491dfccef3296e776d94f6ddb738a6f9a6738917589dced7488c4de11a07c
logsBloom               0xa424015a83a6c914902a74028012293d801080081d1100290209810b5401212514b00102c4060040066470c021088d0102a48970ca203a8034808ad0d332298f0043024803445128c85a141e9801442814a5100f0c5192803c5aa421c0331b0166c0888406225412e044d00900a1cc5c265e442924081fc08004533901081050451021610400104a848921410191004386480001c580449b0260ab4c4a1418300280011b2caa2c290e80efd89d000206020c820044b026200b10c02d906c4c31500044020d8008a203901c0046c1400428185600038b52581c52a12e064220004a39252b50081140a19602084d34488400d281512394c050004088a24c207007
miner                   0x3ecef08d0e2dad803847e052249bb4f8bff2d5bb
mixHash                 0x38133a0f95886cdae8fa9da3055dc409b5677398cde2de386c135c2186959df1
nonce                   5683548311596532688
number                  14558180
parentHash              0x8981b0db340d5ce814fd3e4d4004d8072078b2e32878cd6cbbef7bc4f40f49aa
receiptsRoot            0x02132c612be2bf5d7d218a94e0e80d2054bb436bd9acd3cfd9bf6f2bda8c03a6
sha3Uncles              0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347
size                    57834
stateRoot               0xcce2182138ef57aca2595ef47272939f385f172300e2d2c5f817e9c06857e5d7
timestamp               1649596875
totalDifficulty         0x9be29f08d671e01090e
transactions            ["0xe99c8f16acfe4c30ea1d62fc009c52abda866a209265fd0231125b3f5829bd41","0x0607ceb4e5fccf32785905accd2e0b5337f24e756a64d5b9f71b1bc88763971e","0xa56fdefd60068407dce5b794d6e35fd6175fd4afbe118a4adc4cb4145b737f6e","0x53a31f2dea2e57094185a4e6eba9d56f743a4b9acc66e452904f6eeef69e78d9",
        ...truncated transactions list...
"0x0572abcfdcf3df03e47cfdae391bc6c27daf9a2e1636a78601a6f926ed1a6cb7","0xafaeb7f5caf3458ef0531763642511df8b73078d6d1bd2352977e552d304a4fe","0x75ce9178abcdcd92f30549524a259594cd85d13f0aacfa45100896d769573aca"]
transactionsRoot        0xf5c242a4f103b556008866961ab0962982d26f62420fd3a6aa512c6f454379e9
uncles                  []

The data for this block lists the transactions, gasLimit, gasUsed, and other data about the block. To examine the first transaction in the list, use seth tx 0xe99c8f16acfe4c30ea1d62fc009c52abda866a209265fd0231125b3f5829bd41.

accessList              [{"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","storageKeys":["0x14edb3f2ae9217ac1062a3663ff296e62d08cb5e4c1652d16c28cc4aede04f59","0xa16f6733e9a6e575701fc996707206adb879e7981d0e768b0d54351c053ccb2b"]},{"address":"0xcb9fe6b9f0411ace824d8bf4bba29f03387ff459","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000008","0x9b637a02e6f8cc8aa1e3935c0b27bde663b11428c7707039634076a3fb8a0c48","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b980a","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b9807","0x0000000000000000000000000000000000000000000000000000000000000004","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b9808","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b9809","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000"]},{"address":"0x6f80310ca7f2c654691d1383149fa1a57d8ab1f8","storageKeys":["0xb3fae41a68fb6884ab6ce157e11f00eb8a737b57cf3936718e61999fd88b0185","0x20f0618767b04ed4c26dde2cc235e173c785c75b16fd723e08eacce0b2479536","0xfe3b247b4660499a8b482ec4c25c60ab4e77c2245c27088eb7b038386c6a9842","0xfa52f1eec9dfd6d84689cfc1c47c7527cf9b4ed5dbbecc964f3259310912525b"]}]
blockHash               0xb22491dfccef3296e776d94f6ddb738a6f9a6738917589dced7488c4de11a07c
blockNumber             14558180
chainId                 1
from                    0x5aa17fc7f2950eca85376c3a8cb1509e8e4b39df
gas                     179307
gasPrice                28841430027
hash                    0xe99c8f16acfe4c30ea1d62fc009c52abda866a209265fd0231125b3f5829bd41
input                   0x040012de32ce7fb9c00000000000012346efe5f60f679dbbcb9fe6b9f0411ace824d8bf4bba29f03387ff45913432bb9cc0cb5cd04abdb5c35b7ecd2c4597192a147a3fd138dd38d65064a95
maxFeePerGas            28841430027
maxPriorityFeePerGas    0
nonce                   48597
r                       0x6a88a78634e50895ea891d5561a9ab1d03d83fcb1c8c567d80bb14940cee83b6
s                       0x771e073dc88668ab4d6107b11897e58af131fa478addcb244ea7e33c5ce773bf
to                      0x01ff6318440f7d5553a82294d78262d5f5084eff
transactionIndex        0
type                    2
v                       0
value                   14558180

This data is a bit hard for a human to decrypt on the spot (though maybe not for you, anon), so we can turn to our friend ethtx.info to examine this transaction more closely. If we compare the seth data above to the screenshot below, we find the seth “from” field matches the ethtx.info “Sender” field, the seth “to” field matches the ethtx.info “Receiver” field, etc. The list of values in the seth “accessList” row refer to the state variables stored at those addresses. This is easier to see with the 2nd address in the seth “accessList” row, where storage slot values of 0x2, 0x8, 0x4, 0x1, and 0x0 exist. The “accessList” values that contain a large hex value likely the hashes of functions, mappings, or other data. We can retrieve the data in these storage slots using seth.

ethtx.info

To see the value stored in storage slot 2 of address 0xcb9fe6b9f0411ace824d8bf4bba29f03387ff459, use seth --to-dec $(seth storage 0xcb9fe6b9f0411ace824d8bf4bba29f03387ff459 2). The value returned is identical to the value of the feeGrowthGlobal1x128 uint256 value visible on Etherscan. While it’s often easier for a human to browse Etherscan where the data is human readable, seth provides the data in a more machine-readable format for scripting and automation purposes. Additionally, when Etherscan is unavailable, using seth to connect directly to an RPC endpoint can provide an alternative way to get the same information.

Switching chains

Seth can easily query data from Ethereum testnet chains if you change the JSON RPC endpoint in the ETH_RPC_URL environment variable to an endpoint for your preferred blockchain.

Real-World Examples

The seth documentation one-pager has a long list of CLI flags and arguments with descriptions, but I think it’s more fun to summarize seth features with real-world use cases:

  1. To check the keccak hash of a value, use seth keccak "totalSupply()"
  2. Forgot the value of type(uint32).max or type(int32).max? Use seth --to-dec $(seth --max-uint 32) and seth --to-dec $(seth --max-int 32)
  3. To solve Ethernaut level 8 by performing a storage slot lookup, first set your “ETH_RPC_URL” environment variable to a rinkeby RPC endpoint (Infura or Alchemix provide endpoints if you make a free account), then run this one-liner: seth --to-ascii $(seth storage 0xB32Ef1e8b55FE270Bc665F52076bc677B0D02f8f 1)
  4. To check the balance of vitalik.eth in ETH units (Note: this requires Etherscan API key for ENS address resolution) seth --from-wei $(seth balance $(seth resolve-name vitalik.eth))
  5. Query the totalSupply of WETH and convert the return hex value to decimal, then convert the decimal value from wei units to ether units seth --to-dec $(seth call 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 "totalSupply()")
  6. Querying the nonce for an address should return the number of transactions this address has sent using. To check this value, use seth nonce 0x5aa17fc7f2950eca85376c3a8cb1509e8e4b39df. At the time of writing, the output was 48603.
  7. You can manually perform the same contract verification process that Etherscan does to validate that the source code of a contract matches the deployed bytecode. First, download the deployed bytecode of a contract using seth code <address>. Second, download the contract source code using the method from before of seth etherscan-source <address> | jq .SourceCode -r. Finally, compile the contract code using solc --bin --optimize filename.sol (replace the filename with the filename you chose for the local copy of the contract code). You can then diff the hex binary output of the locally compiled code with the contract’s hex binary data downloaded using seth. One open question I had after trying this out is why the hex binary value of a contract viewed on Etherscan is different from the hex binary value I downloaded using seth. Both values came from the same blockchain, so what gives? If you find the answer, let me know and I can add an explanation here.
  8. seth send is very useful when you want to send transactions to the blockchain. See the official documentation.

There are many further features of seth, but to avoid rehashing the official documentation, I want to keep this short. Lastly, seth has many further use cases when paired with the rest of the dapptools suite, including hevm, dapp, and ethsign.

Concluding Remarks

If you’ve used Etherscan a lot, you’ll notice you can get similar information using seth. This makes sense, because both tools query data from the same blockchain, so there should be a lot of overlap. If you prefer working in the terminal to working in a web browser, seth is a great option. And if you are building an automated pipeline that needs blockchain data, seth is a great tool for this use case.

Etherscan: Everyone's Favorite Blockchain Explorer

If you’ve been around the Ethereum ecosystem for some time, you’ve already used Etherscan before. Etherscan has many useful features, some more hidden than others, so I will start from the beginning. Skip any sections that are old news to you.

Which Chain?

There are blockchain explorers for Ethereum testnets, like rinkeby and goerli, as well as many other blockchains. This article only focuses on the mainnet Etherscan Etherscan website, but most of this information should translate to other block explorers.

Setup

There is no mandatory installation or setup for Etherscan, but it is helpful to create a free account if you will be using the API. Having an account allows you to send more requests to the website before getting rate limited.

More Information

If you have specified Etherscan questions, search the developer docs or the knowledge base. This article will give a broad overview focusing on security use cases.

EoA Address Lookup

First let’s search for an externally owned account (EOA) address, which is an address owned by some user, as opposed to a contract. You can search by ENS name or using the actual address. Here’s what the result of searching for “vitalik.eth” looks like.

vitalik.eth result

Because “vitalik.eth” is an ENS domain, our search tells us what the resolved address of this ENS domain is. If we click on this address, we will see the transactions made by “vitalik.eth”.

vitalik.eth transactions

The two main sections of address information are:

  1. The information tabs at the bottom that list different types of information. The default tab selected is “Transactions”, but there are six other tabs visible: Internal Txns, Erc20 Token Txns, Erc721 Token Txns, Erc1155 Token Txns, Analytics, and Comments.
  2. The Overview section lists the tokens that an address holds. The “Balance” in the screenshot below only includes the value of the Ether the account holds. If you click on the “Token” dropdown menu, a list of ERC tokens held by the account is shown. A cleaner view of the tokens is available by clicking the “View expanded ERC-20 token holding” button to the right of the dropdown menu.

vitalik.eth tokens

Because most security usage of Etherscan involves investigating contracts, searching for an EoA for security purposes is a less common activity.

Contract Address Lookup

You can search for a contract using either the contract address or a descriptor for the contract. To see the results of searching by descriptor, type “WETH” into the search bar but DO NOT submit the search. Instead, after typing “WETH” you will see a dropdown menu of tokens appear below the search bar. You can click the WETH result from the dropdown to get to the WETH contract. As a sidenote, you can even search for queries besides ERC token names, such as the phrase “Compound Governance”.

WETH Search

We can see the information available for this contract looks different than the EoA result from earlier. There is no “Balance” or “Tokens” list, but in its place is the “Price”, “Fully Diluted Market Cap”, “Max Total Supply”, “Holders”, and “Transfers”. And further down, we can see different tabs to view different information.

WETH Contract

Let’s click on the “Contract” tab and examine the WETH contract more closely.

Contract Function Interaction

The “Contract” tab is where most of the security work in Etherscan happens, because that’s where you will find the contract code. Under the “Contract” tab, click the “Expand All” link on the right. The list you will see is a list of contracts in the function.

WETH Contract Functions

If the function does not take input parameters and is a view function, meaning it only reads the blockchain state and does not modify the state (read-only), then the result will automaticaly be shown. For example, the “totalSupply” function outputs the total supply of WETH, and if you refresh the page a few times, you can see the value changing. Other functions, like the “name” and “decimals” functions, have outputs that are hard-coded to the contract and will not change (this is based on extra knowledge of the ERC20 contract code, so Etherscan doesn’t provide this information). If a function is called with input parameters, you can provide those input parameters and click the “Query” button to query the result from the blockchain. To test out this feature, enter the address “0xd8da6bf26964af9d7eed9e03e53415d37aa96045” (the same vitalik.eth resolved address we used earlier) in the input field for the “balanceOf” function and click the “Query” button. The result in this case is in units of wei, because the decimals value was 18. If we divide this value by 10^18, we get the result 0.05 WETH. We can double check our work by searching for the “0xd8da6bf26964af9d7eed9e03e53415d37aa96045” address, clicking on the dropdown menu of Tokens in the upper left, and observing that this address holds 0.05 WETH. You can even connect your Metamask wallet to Etherscan to interact with contract functions directly, but this approach is not often used for security because custom scripts and contracts are more often the tool of choice.

WETH balanceOf

Contract Code

Often the reputable contracts that you will look at in Etherscan have verified source code. Let’s pretend we want to examine the deployed code for MakerDAO, because we’re either doing some fun research or heard they offer a large bug bounty. We can search the MakerDAO developer documentation and GitHub repository to see if they maintain a public list of their actively deployed contracts, and like any major protocol should, they do have such a list. You can pick any contract address, but for this example I will use the Changelog contract at 0xda0ab1e0017debcd72be8599041a2aa3ba7e740f (which is actually named Chainlog in the contract source code). Make sure you have “Ethereum mainnet” selected on this page, because we are using the Ethereum mainnet Etherscan website.

MakerDAO Changelog

When we search for this address in Etherscan and navigate to the “Contract” tab, we see that the “Contract” tab has a green checkmark and contains source code. This is because the contract developers uploaded their contract source code and Etherscan verified that the compiled source code matches the bytecode that is stored on the Ethereum blockchain. The source code is visible in Etherscan.

MakerDAO Source Code

There are some options to the top right of the “Contract Source Code” that can sometimes be useful:

  1. The “Outline” dropdown menu makes it easy to navigate to specific functions in the code
  2. The “More Options” dropdown menu allows you to
    • Find similar contracts with the “Similar” button (with mixed results in my experience)
    • Visualize the contract function calls with the “Sol2Uml” button (see screenshot below)
    • Diff the contract with another contract with the “Compare” button

UML Diagram

Verified source code might be the most used Etherscan feature for security purposes. Scrolling through code on Etherscan is how you look for bugs on mainnet, because the code in the GitHub repository for projects can often be different than what is currently deployed. Most large projects should have verified their source code on Etherscan, because it improves transparency and trust in the project. If the developers did not upload their source code to Etherscan, you can still try decompiling the bytecode using one of the many options for Ethereum decompilers, but this process can get messy and is normally only necessary when analyzing contracts used in hacks.

When contract source code is verified on Etherscan, the contract must be “flattened”. This means that the import statements are removed and the source code for the contracts that were imported and stored in the same file. You will encounter contract code on Etherscan that is very long because of this, and sometimes you need to scroll to the very bottom of the source code, past all of the imported code, to find the custom contract code that you are seeking.

If you feel more at home looking at code in VS Code that in the Contract tab of Etherscan’s website, try using this DethCode viewer. By replacing modifying the Etherscan URL from etherscan.io to etherscan.deth.net, you can view the same contract code in a browser-based VS Code. If you look at code a lot, it’s a nice tool. Try out the interface at https://etherscan.deth.net/.

Contract Events and Analytics

While the “Contract” tab with the source code is where the most time is spent when analyzing security bugs, the “Events” and “Analytics” tabs can sometimes provide additional data. In the “Events” tab for the MakerDAO Changelog contract, we can see the logs for past emit calls. The UpdateVersion emit is in the setVersion() function, and because it takes a string as an input parameter type, we can use Etherscan to decode the value as text. Similarly, the UpdateAddress emit is found in the setAddress() function and take a bytes32 string and address as input parameters, so we can decode these values accordingly.

Contract Events

The MakerDAO Changelog Contract does not hold any value, so the “Analytics” tab does not show anything exciting. If we return to the WETH contract, the “Analytics” tab shows us a lot of data about the token over time.

Contract Analytics

A more interesting Analytics view is found in the Ronin Bridge contract, which suffered a large hack in early 2022. The screenshot below zooms in on the ETH transfers to and from this contract during the month of the hack, March 2022. Want to guess what day the hack occurred on?

Ronin Contract Analytics

Transaction Lookup

Like searching for addresses and contracts, searching for a transaction is also possible. Let’s continue with the Ronin hack, where transaction 0xc28fad5e8d5e0ce6a2eaf67b6687be5d58113e16be590824d6cfa1a94467d0b7 transferred the hacked Ether. Scroll down in the “Overview” tab, select “Click to see More”, and then click the “Decode Input Data” button to view the input data for this transaction. This information provides insight into the input data that the Ronin hacker used to withdraw the ETH from the bridge contract.

Ronin Transaction Data

The “Logs” and “State” tabs can give more insight into what functions were called and what state variables were changed.

Ronin Transaction Logs

However, Etherscan isn’t necessarily the best tool to tell us everything about what is happening in this transaction. Most security power users prefer the ethtx.info site for this purpose. It provides a more granular view of the function call sequence in this transaction, a view that Etherscan doesn’t currently have. Viewing the same transaction on ethtx, we can see the five different validator addresses that were compromised in MainchainValidator.isValidator() => True calls. The requirement for a 5 out of 8 majority to perform this attack is visible in the MainchainValidator.checkThreshold() => True call. This information cannot (yet?) easily be found in Etherscan to the best of my knowledge.

Ronin Transaction ethtx

Block Lookup

You can investigate individual Ethereum blocks in Etherscan too. I have not used this feature much, but it should be useful for MEV researchers. The easiest way to view a block is to click on one of the most recent block numbers on the Etherscan homepage. From there, you can view basic data about the block (timestamp, block reward, uncles, etc.), but clicking on the transactions within the block is where most of the useful information is. From there, you can manually examine the transactions that interest you.

Etherscan Block Transactions

Etherscan API

Finally, one of the most useful features of Etherscan is its API. To get an API key, you need a free account, and you will be able to create an API key from the account dashboard. Some frameworks and command line tools require the Etherscan API key for full functionality. For example, the command line tool seth requires the Etherscan API key saved to the ETHERSCAN_API_KEY environment variable, while some projects or frameworks will require a variable such as ETHERSCAN_TOKEN to be set in the project’s .env file. You can use the API to query accounts, contracts, transactions, and all the same types of data that can be accessed on the website. Unless you are building a new tool, in which case you should read the Etherscan API documentation thoroughly, the most common way the API key is used is by configuring it in whichever tool needs the API key to work properly.

More Features

There are even more features of Etherscan, including charts and statistics of recent blockchain activity, top ERC20/721/1155 tokens, a list of recent block uncles, and more. I haven’t seen these used at all for security purposes, so consult the Etherscan documentation or just click around the website to learn more about these features.