StrataSolidity

Fundamentals

Pre- and Postconditions#

Preconditions and postconditions are the primary way to specify what a function does. A precondition states what must be true when the function is called. A postcondition states what must be true when the function returns.

Preconditions (requires)#

A precondition constrains the inputs and state under which a function is expected to operate correctly:

/// @custom:strata requires amount > 0
/// @custom:strata requires balances[msg.sender] >= amount
function withdraw(uint256 amount) external {
    balances[msg.sender] -= amount;
}

StrataSolidity proves that the function body is correct assuming the preconditions hold. It also checks that every call site satisfies the preconditions.

Postconditions (ensures)#

A postcondition states what the function guarantees upon return:

/// @custom:strata requires amount <= balances[msg.sender]
/// @custom:strata ensures balances[msg.sender] == old(balances[msg.sender]) - amount
/// @custom:strata ensures balances[to] == old(balances[to]) + amount
function transfer(address to, uint256 amount) external {
    require(amount <= balances[msg.sender], "Insufficient");
    balances[msg.sender] -= amount;
    balances[to] += amount;
}

The old() expression refers to the value of a variable before the function executed. See Old Expressions for details.

How verification works#

Given a function with preconditions P and postconditions Q, StrataSolidity proves:

Assuming P holds at entry, after executing the function body, Q holds at exit.

If the proof succeeds, you have a mathematical guarantee — not just confidence from testing. If it fails, either the implementation has a bug, or the specification needs strengthening (e.g., a missing precondition).

Example: access control#

contract Ownable {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    /// @custom:strata requires msg.sender == owner
    /// @custom:strata requires newOwner != address(0)
    /// @custom:strata ensures owner == newOwner
    /// @custom:strata modifies owner
    function transferOwnership(address newOwner) external {
        require(msg.sender == owner, "Not owner");
        require(newOwner != address(0), "Zero address");
        owner = newOwner;
    }
}

StrataSolidity proves that after transferOwnership executes, owner equals newOwner — provided the caller is the current owner and the new owner is not the zero address.

Multiple conditions#

You can have multiple requires and ensures annotations. They are conjoined — all preconditions must hold, and all postconditions are checked:

/// @custom:strata requires amount > 0
/// @custom:strata requires amount <= 1000000000000000000000000
/// @custom:strata ensures totalSupply == old(totalSupply) + amount
/// @custom:strata ensures balances[to] == old(balances[to]) + amount
function mint(address to, uint256 amount) external { ... }

View this page’s source on GitHub