Skip to content

Commit 4360f39

Browse files
Required invariant minimal example (#166)
* Required invariant minimal example * Update CVLByExample/Invariant/RequireInvariant/README.md Co-authored-by: Nurit Dor <[email protected]> * Improve docs * Update README --------- Co-authored-by: Nurit Dor <[email protected]>
1 parent 2df72c7 commit 4360f39

File tree

4 files changed

+98
-0
lines changed

4 files changed

+98
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"files": ["DataInvariant.sol"],
3+
"verify": "DataInvariant:DataInvariant.spec",
4+
"solc": "solc8.25",
5+
"msg": "Verifying data invariants"
6+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/**
5+
* @title DataInvariant
6+
* @dev A contract with a per-address balance array and a function that can break an intended invariant.
7+
*/
8+
contract DataInvariant {
9+
10+
mapping(address => int256) public balance;
11+
mapping(address => bool) public accessInvariant;
12+
13+
/**
14+
* @notice Decreases the address's balance by 2*value and then partially restores it,
15+
* potentially leaving it negative if `balance[a]` wasn't large enough.
16+
*/
17+
function breakInvariant(address a, int256 value) external returns (bool accessInv) {
18+
require(value >= 0, "Value must be nonnegative");
19+
balance[a] -= 2 * value; // Force a large negative change
20+
accessInv = accessInvariant[a]; // Read from the 'accessInvariant' mapping
21+
balance[a] += value; // Restore half, potentially leaving negative
22+
}
23+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// --------------------------------------------------------
2+
// 1. Declare the methods we want to call or inspect
3+
// --------------------------------------------------------
4+
methods {
5+
function breakInvariant(address, int256) external returns bool envfree;
6+
function accessInvariant(address) external returns bool envfree;
7+
}
8+
9+
// --------------------------------------------------------
10+
// 2. Define the invariant: alwaysPositive **should be violated** with the new semantics but should be verified with the old semantics
11+
// --------------------------------------------------------
12+
invariant alwaysPositive(address a)
13+
currentContract.balance[a] >= 0;
14+
15+
// --------------------------------------------------------
16+
// 3. A helper function that enforces the invariant
17+
// --------------------------------------------------------
18+
function safeAssumptions(address user) {
19+
requireInvariant alwaysPositive(user);
20+
}
21+
22+
// --------------------------------------------------------
23+
// 4. Hook: whenever we read `accessInvariant[user]`, call `safeAssumptions(user)`
24+
// --------------------------------------------------------
25+
hook Sload bool b currentContract.accessInvariant[KEY address user] {
26+
safeAssumptions(user);
27+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Data Invariant Example
2+
3+
This folder shows a **simplified** demonstration of the new `requireInvariant` semantics in Certora’s CVL. We have:
4+
5+
1. A **Solidity contract** (`DataInvariant.sol`) that allows negative balances to appear.
6+
2. A **CVL specification** (`DataInvariant.spec`) that declares an invariant requiring **nonnegative** balances.
7+
3. A **configuration file** (`DataInvariant.conf`) used to run the Certora Prover with our spec.
8+
9+
> **Note:** The invariant is not enforced at the time the hook is triggered in versions **before 8**, but it is enforced globally in versions **from 8 on**.
10+
11+
## Overview
12+
13+
- **`DataInvariant.sol`**
14+
A contract that tracks `balance[a]` for each address and includes the `breakInvariant` function.
15+
16+
- **`DataInvariant.spec`**
17+
- **Invariant**: `alwaysPositive(address a)` states `currentContract.balance[a] >= 0`.
18+
The invariant is violated when `breakInvariant` is called under the semantics from version 8 on but not under the semantics before version 8.
19+
- **Hook**: Whenever the contract reads `accessInvariant[user]` (i.e., an SLOAD on `accessInvariant[user]`), we call a CVL function `safeAssumptions(user)` which has a `requireInvariant alwaysPositive(user)`.
20+
21+
**Explanation of `requireInvariant` semantics:**
22+
- In versions **before 8**, `requireInvariant` was only enforced at the specific location where it was used (inside the hook, when `accessInvariant[user]` is read). This means the invariant is not guaranteed to hold globally—only at the hook point.
23+
- a) Negative balances can exist during a transaction, as long as the invariant holds at the hook location.
24+
- In versions **from 8 on**, the `requireInvariant` command is enforced globally—meaning the invariant must hold before any code of a rule or invariant is executed, not just at the hook.
25+
- b) Negative balances are never allowed at any point where the invariant is expected to hold, so the violation is detected.
26+
27+
## How to Run
28+
29+
1. **Command** the Solidity file and run the Certora Prover using the provided config:
30+
```bash
31+
certoraRun DataInvariant.conf
32+
```
33+
34+
## Execution Before Version 8
35+
See the Certora Prover output:
36+
https://vaas-stg.certora.com/output/1512/b774667137d348039ab86be0f3a1b8f8?anonymousKey=cbd717f02b7ea78a275518c7c4ae340282789318
37+
- Expected outcome: The invariant is only checked at the hook, so negative balances can exist temporarily during a transaction, leading to incorrect assumptions about the state of the contract.
38+
39+
## Execution From Version 8 On
40+
See the Certora Prover output:
41+
https://vaas-stg.certora.com/output/1512/09d15b32556c469f9173b33b41e01711?anonymousKey=c1369211cc8d991e1f345d9012ef655dfb495d05
42+
- Expected outcome: The invariant is enforced globally, so any negative balance is detected as a violation, ensuring the contract state always satisfies the invariant.

0 commit comments

Comments
 (0)