Auditor Liability

Understanding the nuances between developer intent and code execution is critical in establishing clear Audit Liability standards in web3.
The expectation of a smart contract audit often exceeds reality, as developers may not fully grasp the concept of defining a user journey or the extent of potential vulnerabilities, which is crucial in establishing clear boundaries for Auditor Liability. Though, when dealing with programming languages, fully comprehending all potential actions of a smart contract can be challenging. Developers aim to ensure their contracts operate as intended and are free from vulnerabilities that could result in loss of funds or unintended outcomes. However, smart contracts operate based on the defined logic principles and syntax of the programming language used, meaning certain operations are strictly bound by these rules. The importance of clearly defining Auditor Liability cannot be overstated, as it directly impacts the trust and security of smart contracts in the web3 ecosystem.
Auditors must go beyond merely identifying potential errors, they need to grasp the developer's intended user-journey and expectations of the dApp (decentralized app - because its an app deployed on decentralized blockchain tech as opposed to centralized tech like Google Cloud or AWS) to ensure the smart contracts behave as expected. For example, in the programming language Solidity, a common issue is integer overflow and underflow, and an integer overflow might be a deliberate feature in some scenarios, like resetting a counter, rather than a vulnerability. Misinterpreting these intentions can lead to misguided corrections and unintended actions. A deep understanding of the developer's goals and the application’s context is essential for auditors to accurately assess and report on the contract's safety and functionality.
Auditor Liability begins with the auditing scope and ends with the implementation of the corrections, so establishing the parameters that define it is necessary in order to understand if an audit is thorough, comprehensive, and utilizing up to-date smart contract best practices. Establishing a standard framework for Auditor Liability requires clear communication of what occurs during an audit, including educating developers on the importance of defining user journeys and security expectations they may not have considered.
What is Integer Overflow?
Integer overflow occurs when a calculation exceeds the maximum value that can be stored in a variable.
In Solidity, for example, the uint8
data type can store values from 0 to 255, so if you add 1 to 255,
it overflows and wraps around to 0, which could be intentional or it could be unintentional and lead to a possible exploit.
uint8 max = 255;
uint8 result = max + 1;
However, in actuality, this is what is occurring:
result = 255 + 1 = 0
What is Integer underflow?
Integer underflow occurs when a calculation goes below the minimum value that can be stored in a variable. Using the same uint8 data type in Solidity, if you subtract 1 from 0, it underflows and wraps around to 255.
uint8 min = 0;
uint8 result = min - 1;
As well, in this scenario, since uint8 cannot store negative numbers, subtracting 1 from 0 causes it to wrap around to the highest value, which is 255.
result = 0 - 1 = 255
One of the benefits of programming languages in smart contracts is that you can reuse other code that is audited and continually tested in real-time, and an example of this is OpenZeppelin which created the SafeMath library. The SafeMath library is available for anyone to use in their contract which makes that particular section of the code more readable and more understood regarding the developers intended actions.
Example with the SafeMath OpenZeppelin import
using SafeMath for uint8;
uint8 max = 255;
uint8 result = max.add(1); // This will revert the transaction
uint8 min = 0;
uint8 result = min.sub(1); // This will revert the transaction
Even though OpenZeppelin's SafeMath exist for Solidity, it is not present in all pogramming languages for various reasons, though some are because it is not needed. For example, in Vyper, Tezos, and Move integer overflow and underflow are inherently prevented as the language includes built-in checks that revert transactions on such operations. This design choice simplifies development and enhances security, but it still necessitates a comprehensive audit to ensure there are no other logical errors or overlooked vulnerabilities.
Comparison of programming languages
Language | Integer Overflow/Underflow Vulnerability | Mitigation Method |
---|---|---|
Solidity | Yes (pre-0.8.0) / No (0.8.0+) | Library (SafeMath) / Syntax (0.8.0+) |
Vyper | No | Syntax |
Rust | Yes | Syntax (Debug mode)/Library (Release mode) |
C++ | Yes | Manual / Library / Compiler options |
Tezos (Michelson) | No | Syntax |
Move (Libra) | No | Syntax |
Cairo | No | Syntax |
Bitcoin Script | No | Syntax |
So how do we define Auditor Liability?
While tools like OpenZeppelin's SafeMath exist, Auditor Liability includes assessing whether developers have appropriately implemented such best practices, and educating them on the historical context and importance of these standards.
Auditor Liability encompasses not only identifying vulnerabilities but also bridging the gap between a developer's often incomplete vision and the actual code implementation, necessitating a deep understanding of both intended functionality and potential exploit history. This is crucial in smart contract auditing as both parties should rely on the audit with almost equal weight. Furthermore, continuous education and updates on evolving best practices and emerging vulnerabilities are vital for both developers and auditors to maintain a high standard of security in smart contract deployments.
Auditor Liability is not just about identifying flaws; it’s about understanding and documenting the logic behind every decision made in the code. As such, we are suggesting the use of a Logic Tree as a roadmap, laying out each step in the contract’s execution and highlighting areas where risks might emerge. By incorporating this approach, both developers and auditors can ensure a more transparent and secure deployment process.
Logic Tree
To illustrate how a logical tree could define the intended outcome of smart contracts, we will use the standard ERC20 token as an example below:
ERC20 Token Contract Initialization in Solidity
│
├─ Constructor: ERC20("MyToken", "MTK")
│ ├─ Set Token Name: "MyToken"
│ │ ├─ Language Syntax: Not applicable
│ │ ├─ Asset Layer Risk: None
│ │ ├─ Admin Layer Risk: None
│ │ └─ Risk Mitigation Techniques: Not applicable
│ │
│ ├─ Set Token Symbol: "MTK"
│ │ ├─ Language Syntax: Not applicable
│ │ ├─ Asset Layer Risk: None
│ │ ├─ Admin Layer Risk: None
│ │ └─ Risk Mitigation Techniques: Not applicable
│ │
│ └─ Mint Initial Supply: 1000000 * 10 ** decimals()
│ ├─ Language Syntax: Solidity 0.8.0+ prevents overflow/underflow
│ ├─ Asset Layer:
│ │ ├─ Vulnerability: Token supply manipulation
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use OpenZeppelin's ERC20 standard
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Implement supply cap
│ ├─ Admin Layer:
│ │ ├─ Vulnerability: Unauthorized minting
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use Ownable contract
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Implement multi-sig wallet
│ │ ├─ Vulnerability: Coordination failure
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Not applicable
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Not applicable
│ │ ├─ Vulnerability: Single point of failure for token issuance
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Not applicable
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Implement multi-sig wallet
│
├─ ERC20 Functionality (OpenZeppelin)
│ ├─ Balance Of
│ │ ├─ Language Syntax: Not applicable
│ │ ├─ Asset Layer Risk: None
│ │ ├─ Admin Layer Risk: None
│ │ └─ Risk Mitigation Techniques: Not applicable
│ │
│ ├─ Transfer
│ │ ├─ Language Syntax: Not applicable
│ │ ├─ Asset Layer:
│ │ │ ├─ Vulnerability: Token transfer manipulation
│ │ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use OpenZeppelin's ERC20 standard
│ │ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Not applicable
│ │ ├─ Admin Layer:
│ │ │ ├─ Vulnerability: Unauthorized transfers
│ │ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use Ownable contract
│ │ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Implement multi-sig wallet
│ │ ├─ Vulnerability: Coordination failure
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Not applicable
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Not applicable
│ │ ├─ Vulnerability: Single point of failure for token issuance
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Not applicable
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Implement multi-sig wallet
│ │
│ ├─ Allowance
│ │ ├─ Language Syntax: Not applicable
│ │ ├─ Asset Layer:
│ │ │ ├─ Vulnerability: Token allowance manipulation
│ │ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use OpenZeppelin's ERC20 standard
│ │ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Not applicable
│ │ ├─ Admin Layer Risk: None
│ │ └─ Risk Mitigation Techniques: Not applicable
│ │
│ ├─ Approve
│ │ ├─ Language Syntax: Not applicable
│ │ ├─ Asset Layer:
│ │ │ ├─ Vulnerability: Token approval manipulation
│ │ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use OpenZeppelin's ERC20 standard
│ │ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Not applicable
│ │ ├─ Admin Layer Risk: None
│ │ └─ Risk Mitigation Techniques: Not applicable
│ │
│ └─ TransferFrom
│ ├─ Language Syntax: Not applicable
│ ├─ Asset Layer:
│ │ ├─ Vulnerability: Unauthorized token transfers
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use OpenZeppelin's ERC20 standard
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Not applicable
│ ├─ Admin Layer:
│ │ ├─ Vulnerability: Unauthorized transfers
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Use Ownable contract
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Implement multi-sig wallet
│ │ ├─ Vulnerability: Coordination failure
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Not applicable
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Not applicable
│ │ ├─ Vulnerability: Single point of failure for token issuance
│ │ │ ├─ Risk Mitigation - Negate through open-source standard: Not applicable
│ │ │ ├─ Risk Mitigation - Negate through manual code prevention: Implement multi-sig wallet
This example further illustrates how a developer could benefit from using a logic tree when writing their smart contracts and especially when they provide an auditor with their code. Here is a fictitious example:
Mock Kingdom Smart Contract
│
├─ Initialize Game (Constructor)
│ ├─ Set owner
│ │ ├─ Asset Layer Risk: None
│ │ └─ Administrative Risk: Centralization (Known since Ethereum's inception, 2015)
│ │ ├─ Mitigation 1: Implement multi-sig wallet (Introduced by BitGo in 2013)
│ │ │ └─ Resulting Risk: Coordination failure
│ │ │ └─ Potential Outcome: Delayed decision-making
│ │ └─ Mitigation 2: Implement DAO governance (First major DAO: The DAO, April 30, 2016)
│ │ └─ Resulting Risk: Voter apathy
│ │ └─ Potential Outcome: Minority control
│ │
│ ├─ Initialize player stats (use SafeMath)
│ │ ├─ Asset Layer Risk: Integer overflow/underflow (BEC Token hack, April 22, 2018)
│ │ │ ├─ Mitigation 1: Use OpenZeppelin's SafeMath library (SafeMath became a library: April 19, 2017)
│ │ │ │ └─ Resulting Risk: Increased gas costs
│ │ │ │ └─ Potential Outcome: Higher transaction fees
│ │ │ └─ Mitigation 2: Use Solidity 0.8.x built-in overflow checks (Released: December 16, 2020)
│ │ │ └─ Resulting Risk: Compatibility issues with older compilers
│ │ │ └─ Potential Outcome: Limited portability
│ │ └─ Administrative Risk: None
│ │
│ └─ Mint initial game tokens (check for overflow)
│ ├─ Asset Layer Risk: Token supply manipulation (Highlighted by SmartBillions hack, October 2017)
│ │ ├─ Mitigation 1: Implement supply cap (Used in Bitcoin protocol since 2009)
│ │ │ └─ Resulting Risk: Inflexibility in token economics
│ │ │ └─ Potential Outcome: Difficulty in adjusting to game growth
│ │ └─ Mitigation 2: Implement minting limits per time period (Used in MakerDAO, December 2017)
│ │ └─ Resulting Risk: Complex tracking mechanism
│ │ └─ Potential Outcome: Increased contract complexity
│ └─ Administrative Risk: Unauthorized minting
│ └─ Mitigation: Implement role-based access control (OpenZeppelin, 2018)
│ └─ Resulting Risk: Centralization of minting authority
│ └─ Potential Outcome: Single point of failure for token issuance
│
├─ Join Game (Function)
│ ├─ Check if player exists
│ │ ├─ Yes: Revert transaction
│ │ │ ├─ Asset Layer Risk: Denial of Service (DoS) (GovernMental Ponzi scheme, April 2016)
│ │ │ │ ├─ Mitigation 1: Implement cooldown period (CryptoKitties, November 28, 2017)
│ │ │ │ │ └─ Resulting Risk: Legitimate players locked out
│ │ │ │ │ └─ Potential Outcome: Reduced player onboarding
│ │ │ │ └─ Mitigation 2: Implement gas-efficient existence check (EIP-1052, May 2018)
│ │ │ │ └─ Resulting Risk: Increased storage costs
│ │ │ │ └─ Potential Outcome: Higher deployment/operation costs
│ │ │ └─ Administrative Risk: None
│ │ │
│ │ └─ No: Create new player
│ │ ├─ Asset Layer Risk: Resource exploitation (Multiple game hacks, e.g., Fomo3D, July 2018)
│ │ │ ├─ Mitigation 1: Implement progressive resource allocation (Axie Infinity, 2018)
│ │ │ │ └─ Resulting Risk: Complex onboarding process
│ │ │ │ └─ Potential Outcome: New player confusion
│ │ │ └─ Mitigation 2: Implement initial resource limits (Common in DeFi since Compound, September 2018)
│ │ │ └─ Resulting Risk: Initial gameplay limitations
│ │ │ └─ Potential Outcome: Reduced early-game engagement
│ │ └─ Administrative Risk: None
│ │
│ └─ Emit NewPlayerEvent
│ ├─ Asset Layer Risk: Event spam (Ethereum network congestion, CryptoKitties, December 2017)
│ │ ├─ Mitigation 1: Implement event throttling (EIP-1699 proposed, October 2018)
│ │ │ └─ Resulting Risk: Delayed event propagation
│ │ │ └─ Potential Outcome: Inconsistent front-end updates
│ │ └─ Mitigation 2: Batch event emissions (Uniswap V2, May 2020)
│ │ └─ Resulting Risk: Increased transaction size
│ │ └─ Potential Outcome: Higher gas costs for users
│ └─ Administrative Risk: None
A logic tree like the one above is a way to define and outline the steps and actions a developer takes to illustrate how they expect their code to operate as intended. This means defining the logical barriers that are needed to prevent errors and unintended consequences and the logical tree above provides a solution to that.
Auditor Liability should include a review of whether the developers have followed best practices, such as using well-audited libraries like OpenZeppelin, and whether the code aligns with the intended user-journey and security protocols.
How do we hold the auditor accountable and define liability?
At this point, a logic tree only holds the developer accountable, though the intention is for a logic tree to establish accountability for both the developer and the auditor. And this is especially important because with all profession's whose business is their professional opinion, liability must be clearly defined so that it can be clearly enforced.
In other professions there are boards, committees, and governing bodies to establish and define professional liability and the same must be done with web3 auditors. The logic tree above is a basis to defining and implementing auditor liability and should be used accordingly. By creating a web3 agency the government can address the growing need to hold auditors accountable for their professional opinions and to establish more trust in web3 that is greatly needed.
Sources
- OpenZeppelin: https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#IERC20
- Vyper Programing Language: https://docs.vyperlang.org/