Smart Contract Audit: An Auditor’s Story

What to Expect when your Smart Contract is Audited:
The Auditor’s Story

Considering a Smart Contract Audit?As a smart contract auditor, I’m trying to make sure your contract is secure against Ethereum Virtual Machine (EVM) level attacks. Those are the kinds of attacks that leverage the nuances of the EVM’s logic in order to hack your smart contract. What I am not looking at is your business logic. That job falls to your test writers. I can do that, DApp developers are polymaths by definition, but that requires a lot more hours and should be scoped out separately.

Specifically, I’m targeting the list of known attacks published by Consensys and the Ethereum Foundation.

  • Race Conditions (Reentrancy and Cross-function Race Conditions)
  • Transaction-Ordering Dependence (TOD) / Front Running
  • Timestamp Dependence
  • Integer Overflow and Underflow (including Storage Manipulation)
  • Denial of Service with Unexpected Revert
  • Denial of Service with Block Gas Limit
  • Forcibly Sending Ether to a Contract

(A full description of these attacks can be found at https://consensys.github.io/smart-contract-best-practices/known_attacks/ and the definitive lecture of 2018 on this subject by Bernhard Mueller can be found at https://youtu.be/iqf6epACgds)

Compile and expose contract API

Now the first thing I am going to do is test your smart contract, ideally, with the test scripts you have already written. If I don’t have access to those I’ll compile your contracts in either Truffle or Remix in order to make sure the contract compiles and exposes the functions that have been detailed in the contract specification.

Ensure dependencies are accessible

Once I know I’m working with a functioning smart contract, there are a few steps I need to take in order to make sure my auditing application will recognize every dependency that your contract imports. I need to go grab the code of any dependency that is already deployed and then link it locally. If the framework your code is working in has any issues with pathing, I’ll copy your dependencies into the same folder as your contract and redirect the import statements to this copy. And then I’ll need to do the same for every dependency that I copied, because they will often times also have dependencies that I need to expose locally.

Explore every possible interaction

At this time in the smart contract audit, I’ll spin up Docker and run a local instance of my auditing application and give it the command to run a whole bunch of simulated interactions with your contract using symbolic execution. What this does is explore every possible interaction an actor can perform with your contract using an abstraction of any given input. So instead of testing every possible value that can be passed to every function, which would take longer than the heat death of the universe, it passes a symbol that stands for every possible input. This frees up time for the software to test all possible recursion in your contract’s flow control. The standard depth of recursion that I test for is 8 levels of recursion, but if we will be conducting a formal audit, I’ll run much deeper in an attempt to falsify any mathematical proofs required by a formal audit.

Trivial and non-trivial vulnerabilities

Once the smart contract audit application has finished exploring all the possibilities it will return a report that I will then convert to an appropriate file format; usually markdown, but some projects require JSON reports in order to be parsed by 3rd party applications. This report will contain both trivial and non-trivial vulnerabilities. The trivial ones are technically vulnerabilities, but are generally not exploitable. For example, if your contract is built atop OpenZeppelin smart contracts they could theoretically have malicious business logic, but if we check and see that OpenZeppelin is an accepted standard and does not have any delegate calls that will alter the behavior of the source contracts, we know that we are safe.

Non-trivial vulnerabilities are usually exposed by your business logic. A common one I see is Integer Overflow arising from a for loop that works with arrays. In this case, the business logic may be sound in that controls are in place that no combinations of array lengths or iterations through the for loop will result in an array length exceeding 2**256. But I would advise the client to request or perform a formal audit on that particular function in order to mathematically prove that there is no intersection between control logic boundaries and statistical vulnerability frontiers… or at least that the probability of their intersecting within a given timeframe is within the client’s risk tolerance.

Formal Auditing Recommendation

Finally, I’ll write up my “Formal Auditing Recommendation” in which I identify those functions that expose non-trivial vulnerabilities and suggest that they either be formally audited or rewritten in order to preclude those vulnerabilities. Only the client and their development team can determine which option is most appropriate. As a general rule, though, you want your smart contract to require as few proofs as possible so it’s usually wise to go for the rewrite first and follow that with a second smart contract audit.

Auditing and testing are the pillars of good contract security. If you are handling money in your contracts you should definitely seek out those services from experts. And, if you are looking for some experts in blockchain solutions, you’re always welcome to reach out to my team at web3devs!