Preparing For An Audit

Security audits are a critical step when deploying secure smart contracts, but they’re not a magic spell to remove all bugs. Audits are a two-way collaborative effort between the auditors and the protocol team to find and fix as many vulnerabilities as possible to reduce the risk of exploits by an attacker.

Thoughtful preparation for an audit maximizes its value by allowing the auditors to maximize the amount of time applying their unique skills. Ideally, your auditors will be spending most of their time looking for critical bugs, weird edge cases or attack vectors instead of correcting typographical errors or trying to decipher a poorly-structured codebase.

Step by step, here’s the comprehensive guide to getting ready for a smart contract audit. Let’s start with the heart of the protocol: the code.

1. The Code

The quality of the codebase is the single biggest factor affecting the value of the audit. A high quality codebase is:

  • Complete
  • Clean
  • Simple
  • Logically divided
  • Idiomatic

Complete: All business logic should be implemented and mature. No parts of the protocol should be left as mockups.

Clean: The codebase should be free of commented code sections, unused imports or other debris. Comments should be up-to-date and used when necessary. A linter or formatter should be used to ensure consistent formatting across the codebase. The contracts should be free of typographical errors like misspellings or comments not matching implementation. Style and conventions should be consistent within the codebase. Examples of good practices:

  • Setter functions across multiple contracts are named descriptively following a similar pattern
  • Function arguments, local variables, state variables, and constants each have unique variable name formats
  • Names of modules are consistent, ie. “pool”, “pair”, and “vault” are not synonyms for each other
  • Access control roles are descriptive and consistent across multiple contracts

Simple: Even if what they do is actually quite complex, core functions should have a clear, logical flow and present no barriers to understanding by a third party. Avoid deeply nested logical trees if possible. Abstracting logic out into internal functions can be helpful to improve readability of larger contracts.

Simplicity also covers the design language of the protocol. As an example, the Maker protocol has a very unique naming convention, with function names such as flip, flop, wag, pot, wad, and punt. While this language is internally consistent, it’s quite inaccessible to third parties. A descriptive function name like addUserToDepositors is easier to audit than addUser.

Logically divided: The job of each contract within the protocol should be clear and distinct. Contracts should not try to do too much all at once. Separate functions into auxiliary contracts as needed.

Idiomatic: Where it makes sense, the code should be written in according to common patterns and accepted best practices. This makes it easier to determine key differences and similarities between your implementation and other implementations with known strengths or weaknesses. The code should be secured against common or well-known vulnerabilities. Problematic patterns like for loops should be avoided if possible, and should use best practices if implemented. All code should be written in the Checks-Effects-Interactions pattern.

The better the codebase, the more thoroughly it can be examined. A protocol that is mature, straightforward, and logically laid-out, with clean, high-quality code presents no obstacles to mastering it, and the auditors can work at maximum efficiency.

2. Tests

Diligent testing is crucial! Auditors will catch business logic errors, but it’s better for everyone if you carefully test to ensure your protocol is working as designed. This lets the auditors focus on finding advanced exploits.

  • Strive for 100% unit test coverage using a test environment like Hardhat or Foundry.
  • Use mainnet fork testing to model interactions with other protocols (if applicable).
  • Deploy to a testnet and do manual testing.
  • The tests should cover realistic usage scenarios, with multiple users, varied transaction amounts, etc.
  • Test the positive and negative cases, ie. it SHOULD do X, and it SHOULD NOT do Y.
  • Get creative! See if your contracts can withstand a flash loan attack, for instance.
  • Static analysis with a tool like Slither or MythX can be useful as a final QC step. These have a similar place in the cycle to running a formatter.
  • Fuzzing is not strictly required, but it is very nice to have! If you do fuzzing, we recommend Echidna in Foundry mode.

Don’t forget to provide package files and instructions to run the test suite! Auditors may run your tests to verify for themselves, and your test cases provide a base to quickly prove/disprove potential vulnerabilities. This is especially important if your protocol depends on interactions with other contracts, as these are time consuming to set up a proof of concept from scratch.

3. Documentation

Clear and complete documentation facilitates the audit process by ensuring auditors do not have to spend hours reverse-engineering your protocol from raw Solidity code. The clearer auditors are on your business goals for the contract, the better they are able to determine if a potential vulnerability is applicable or not.

For best results, documentation should be available at multiple levels of detail.

  • High level: An executive overview of what your protocol is and how it works, to give auditors a strategic understanding of your business case. If the audit is covering only a portion of your codebase (ie. adding a product or feature) you should explain how the in-scope contracts relate to the larger whole.
  • Medium level: Diagrams and plain-language descriptions of how information, assets, and control flows between all the contracts in scope (and any relevant out-of-scope ones). This helps auditors understand your design in terms of what contracts should be talking to who, and when, for a given business task.
  • Low level: Implementation-level specifics such as plain-language descriptions of the permission structure of a contract, how a contract’s data is structured, or how it will be operated in production. This will help auditors understand the contract and detect violations of design intent.
  • In-line: Natspec and inline comments should be used where applicable to clarify complex sections of code and provide a ready reference to third-party readers. In-line comments and natspec should be value additive, not just repeating obvious things like variable names.
  • Self-documenting design: The code itself should be cleanly formatted, with consistent design language and a clear logical flow. This dramatically improves third-party readability, and reduces the need to reference external documentation.

Most documentation, especially high-level, can be written in markdown files, a GitBook, or your preferred documentation solution. Only what is necessary should be written in-line with the code itself, to reduce clutter.

The process of writing the documentation is also a valuable opportunity to self-review your code, and see if you missed anything. Every incremental improvement to the quality of the codebase going into the audit has a compounding benefit to the quality of the production code.

4. Peer Review

The quality of a codebase can greatly benefit from peer review during the development cycle. Bring in an outside senior developer, or have developers on the team review each other’s work. Many subtle issues (especially those related to business logic) can be caught by a set of fresh eyes, before the protocol even goes to the auditors.

Peer review can be done at any stage, and should be done as a best practice. If this is not routinely done, we recommend you do at least one round towards the end of your quality control phase when you’re getting ready for the audit. This final check helps ensure that your contracts are complete and ready to go.

5. Timing

Security audits are the most valuable when they are conducted on the final code that will be deployed.

If changes are made after an audit, then, by definition, you have unaudited portions of your codebase. That’s why it’s critical to not only get an audit after your code is complete, frozen, and ready to be deployed, but engage an audit firm which does resolution rounds.

A good audit package will include a review of fixes to the codebase should any issues be found in the initial audit. A heartbreaking number of protocols have experienced attacks after failing to correctly implement a fix. Don’t be one of them, get your changes reviewed!

While audits are best done just before deployment, you can benefit from contacting auditors earlier in the development process. A short consultation with security experts during the design phase can identify systemic risks in your architecture, and provide guidance on how to avoid them.

Booking your audit in advance helps ensure audit availability when your protocol is ready. Once your core feature set is complete and you are transitioning into the QC and launch preparation phase, reach out to auditors and make agreements with the firm you want to partner with.

Finally, schedule your audit when your team is available! Auditors may reach out with questions during the audit process to clarify design intent or seek additional information. A long response time will slow progress and degrade the effectiveness of the audit.

6. Selecting the right auditor

When you’re ready for your audit, you need to select which audit firms you’re going to work with!

Choosing a firm (or firms) with good qualifications and which matches your needs is crucial to the audit outcome. It’s not a decision that should be purely based on price. Here’s some important things to keep in mind.

Competence and Track Record: Not all firms are created equal. Avoid companies with a history of exploits against their audited protocols, or ones who rely on junior auditors running automated tools. Unfortunately, some audit companies exist mainly as a marketing tool to let protocols say they’ve been audited. If you’re serious about securing your code, you should avoid these types. Here’s a list of major audit firms including both recommended companies, and ones to avoid.

Fit and Philosophy: Security audits are not antagonistic! The development team and the auditors should both want the same thing – a secure, fully-functional protocol. A spirit of cooperation and transparency goes a long way, and makes the experience more enjoyable for all parties. Pick a team you can work well with, don’t compromise on competence.

Familiarity with your application: A good audit firm can diligently examine any type of protocol. That said, if you’re launching a lending protocol, it’s a bonus if your auditors are experienced with lending protocols. Inquire about their previous history in your niche, but don’t let it be a dealbreaker if they’re otherwise qualified.

High-value applications: For critical infrastructure like bridges, or protocols with high TVL, it may make sense for you to engage multiple audit firms to do a redundant audit of your codebase. A double-blind audit gives increased confidence that nothing has been missed in your critical systems. You can also consider engaging formal verification specialists like Certora or Runtime Verification as a supplement to a traditional audit.

Resolution rounds: Many protocols have suffered loss after failing to correctly fix issues identified during the primary audit. Not all auditors include a second round of checks to ensure your fixes are securely implemented. If you weren’t clear up front, this can come as an upsell or “gotcha”. Paladin feels strongly that resolution rounds should be industry standard, and we are proud to include resolution rounds in all of our audits.

Live matching: While not ideal, sometimes changes do get made between the resolution round and contract deployment. Comparing the code as audited to the code as deployed – live matching – has prevented serious bugs in the past. Paladin is one of the few firms which offers live matching with all audits. This extra step of diligence can detect bugs before it’s too late, and provides protection against serious issues such as a malicious developer sneaking a backdoor into the code after the audit.

No matter the application, security audits help protect everyone. The team’s reputation, the users’ funds, and the project’s future all rest upon the security of the smart contracts. It’s worth investing in.

Partner with a firm who can work alongside you to deliver the best product possible. Paladin’s experienced team of professional auditors share your team’s goal of a safe and successful launch. Get a quote today.