Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
59216e6
Initial sanity checks
ericeil Nov 5, 2025
1524fde
WIP
ericeil Nov 7, 2025
28cb32d
get_sui_amount equlivalence
ericeil Nov 11, 2025
eec9a68
WIP
ericeil Nov 24, 2025
9b0cd48
WIP
ericeil Nov 24, 2025
02f5e19
fix: duplicate summary for system state
kel-certora Jan 9, 2026
049204e
Create dummy implementation for parametric rules
kel-certora Jan 9, 2026
b78d4b0
wip: solvency
kel-certora Jan 9, 2026
5c72cbe
Disable system summaries
kel-certora Jan 9, 2026
8e27545
Solvency: Assume only one validator
kel-certora Jan 9, 2026
e5c6af9
Summarize exchange rate
kel-certora Jan 9, 2026
4126f9d
Update patches
kel-certora Jan 9, 2026
912f642
Verify correct accounting of total sui supply
kel-certora Jan 9, 2026
29b71a3
Remove invalid rule declaration
kel-certora Jan 12, 2026
5683bf8
Fix solvency rules
kel-certora Jan 12, 2026
87cf97a
Add basic integrity rules
kel-certora Jan 12, 2026
4907a65
Add integrity rules
kel-certora Jan 12, 2026
a6db8aa
Add rules for validator info consistency
kel-certora Jan 13, 2026
0ad0794
Initial CI setup
kel-certora Jan 13, 2026
5b5b0b9
Restructure rules
kel-certora Jan 13, 2026
7f4f7c2
Remove invalid summary
kel-certora Jan 13, 2026
fd5ab55
Fix no sui iff no lst rule
kel-certora Jan 13, 2026
dfdd5d3
Fix spurious cex in solvency rules
kel-certora Jan 13, 2026
f2edd4e
Value conservation rules
kel-certora Jan 13, 2026
8bfbd27
Merge branch 'kel/specs' into kel/ci
kel-certora Jan 13, 2026
663974d
ci: Update conf files
kel-certora Jan 13, 2026
a699628
ci: print sui version
kel-certora Jan 13, 2026
76d71ad
ci: Fix sui version 1.53.2
kel-certora Jan 13, 2026
720310c
Fix timeouts
kel-certora Jan 13, 2026
6f7841b
Fix timeouts
kel-certora Jan 13, 2026
7d5811c
Merge branch 'kel/specs' of github.com:Certora/liquid-staking into ke…
kel-certora Jan 13, 2026
da285a2
Merge branch 'kel/specs' into kel/ci
kel-certora Jan 13, 2026
4018ae1
Add accounting integrity rules
kel-certora Jan 13, 2026
b990519
Cleanup
kel-certora Jan 13, 2026
6cac394
Rules for fees
kel-certora Jan 14, 2026
d9a8f0b
Fix timeout in arbitrage opportunity rule
kel-certora Jan 14, 2026
2da71bc
wip: add liveness rule for redemptions
kel-certora Jan 14, 2026
032dcc4
Disable nondet summary of vector::contains
kel-certora Jan 14, 2026
3be1fad
chore: rename
kel-certora Jan 14, 2026
debe0db
chore: rename
kel-certora Jan 14, 2026
29c144f
Merge branch 'kel/specs' of github.com:Certora/liquid-staking into ke…
kel-certora Jan 14, 2026
1f272ef
Merge pull request #3 from Certora/kel/no_contains_summary
kel-certora Jan 14, 2026
8f26bf3
Simply fresh storage setup
kel-certora Jan 26, 2026
2cb08b7
chore: munge visibility
kel-certora Jan 26, 2026
024f61c
fix: timeouts in supply increase/decrease rules
kel-certora Jan 26, 2026
933ff22
Verify soundness of refresh summary
kel-certora Jan 26, 2026
c3d0c72
nondet summary for version upgrade
kel-certora Jan 26, 2026
c2ce0d0
Add rule for spread fee rules
kel-certora Jan 26, 2026
a1ae0ca
fix[ci]: renamed confs
kel-certora Jan 26, 2026
84ff6d8
fix: bad module name
kel-certora Jan 26, 2026
9a10cde
fix: increase smt timeouts
kel-certora Jan 26, 2026
59bf0ff
fix: validator consistency soundness and missing rule
kel-certora Jan 27, 2026
70e2493
chore: add comments
kel-certora Jan 27, 2026
d8048d0
chore: polishing
kel-certora Feb 3, 2026
91386c2
fix: invalid configurations
kel-certora Feb 3, 2026
24b4039
chore: Split CI steps to not hit resource limits
kel-certora Feb 4, 2026
fbfccf4
chore: Update CI job names
kel-certora Feb 4, 2026
a5a265d
Merge pull request #2 from Certora/kel/specs
kel-certora Mar 3, 2026
54d1eb6
Merge branch 'main' into kel/fixes-bump
kel-certora Mar 3, 2026
6ec5789
fix: Sui supply reconciles only in next epoch
kel-certora Feb 4, 2026
e426e1e
fix: validator consistency invariant only holds across epoch boundary
kel-certora Feb 5, 2026
d7000de
chore: disable vacuity checks to mitigate timeout
kel-certora Feb 6, 2026
3cf05a4
chore: more accurate rule messages
kel-certora Feb 6, 2026
067cb0a
Move CI test to Sui v1.62.1 and update certora dependencies to match
ericeil Feb 6, 2026
aa3728f
chore: update comments
kel-certora Feb 9, 2026
973e338
fix: Adjust patches
kel-certora Mar 3, 2026
a3b5c25
chore: add package_summaries to gitignore
kel-certora Mar 3, 2026
4acfdfd
chore: force Sui dependencies to match the versions in cvlm/certora_s…
kel-certora Mar 3, 2026
6b62b73
chore[ci]: bump sui version
kel-certora Mar 3, 2026
73ca29b
fix: method matching in confs
kel-certora Mar 3, 2026
e6c4577
chore[ci]: trigger on PR against main
kel-certora Mar 3, 2026
99a7502
Merge pull request #5 from Certora/kel/fixes-bump
kel-certora Mar 3, 2026
58c304b
chore:
kel-certora Mar 3, 2026
a36c8e6
chore: add readme
kel-certora Mar 3, 2026
e625195
Merge pull request #7 from Certora/kel/cleanup
kel-certora Mar 3, 2026
97e28f6
Bump to sui 1.65.2
kel-certora Mar 4, 2026
fa2f924
Pin cli version in CI
kel-certora Mar 4, 2026
f9eb076
Merge pull request #8 from Certora/kel/sui-1.65.2
kel-certora Mar 4, 2026
d32b2de
fix[ci]: missing conf
kel-certora Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/certora.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Certora Prover

on:
pull_request:
branches:
- main
- certora
workflow_dispatch:

jobs:
certora_run:
runs-on: ubuntu-latest
permissions:
contents: read
statuses: write
pull-requests: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install sui
run: |
curl -sSfL https://raw.githubusercontent.com/Mystenlabs/suiup/main/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
suiup install -y sui@1.65.2
- name: Sui version
run: |
sui -V
sui move -V
- name: Apply patch
run: ./certora/munges/munge.sh
- name: Submit Jobs to Certora Prover (Part I)
uses: Certora/certora-run-action@v2
with:
cli-version: "8.10.1"
cli-release: beta
ecosystem: sui
working-directory: certora/spec/
configurations: |-
confs/supply_control_decreases.conf
confs/supply_control_increases_p1.conf
confs/supply_control_increases_p2.conf
confs/accounting_no_lst_no_sui.conf
confs/accounting_total_sui_supply.conf
confs/accounting_value_conservation.conf
confs/fees.conf
job-name: "Verification Part I"
certora-key: ${{ secrets.CERTORAKEY }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Submit Jobs to Certora Prover (Part II)
uses: Certora/certora-run-action@v2
with:
cli-version: "8.10.1"
cli-release: beta
ecosystem: sui
working-directory: certora/spec/
configurations: |-
confs/no_arbitrage.conf
confs/solvency.conf
confs/solvency_monotonicity.conf
confs/validators_consistency_p1.conf
confs/validators_consistency_p2.conf
confs/validators_integrity.conf
job-name: "Verification Part II"
certora-key: ${{ secrets.CERTORAKEY }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7 changes: 7 additions & 0 deletions certora/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# certora
.certora_internal
.venv/
emv-*/
build/
Move.lock
package_summaries
87 changes: 87 additions & 0 deletions certora/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Certora Formal Verification: Suilend Liquid Staking

This directory contains Certora's formal verification of the Suilend liquid staking protocol, written in Move on Sui.

## Directory Structure

### `spec/`

The primary verification package. Contains all specification files (written in Move using the `cvlm` framework), per-rule configuration files, and package metadata.

- `spec/sources/`:Specification modules. Each `.move` file encodes one or more verifiable properties as rules.
- `spec/confs/`:Per-rule Certora Prover configuration files (`.conf`), one per property group.
- `spec/Move.toml`:Package manifest for spec sources. References the `liquid_staking` contract, the `cvlm` (Certora Verification Language for Move) library, Certora Sui framework summaries, and overrides for Sui system packages.

### `munges/`

Code modification scripts and patch files. These make internal functions accessible to the Prover by widening their visibility, and reduce `MAX_VALIDATORS` to make verification tractable.

- `munge.sh`:Applies all patches before running verification.
- `unmunge.sh`:Reverts all patches.
- `record_patches.sh`:Regenerates patch files from current working-directory diffs against HEAD.
- `fees.patch`:Makes `validate_fees()` public.
- `storage.patch`:Widens visibility of view functions and internal helpers; reduces `MAX_VALIDATORS` from 50 to 5.

> [!NOTE]
> The patches are applied as git patches and must be kept in sync with the source code. If the patched files change, `munge.sh` will fail to apply and verification will not run. Use `record_patches.sh` to regenerate the patches after updating the source.

### `assumptions/`

A separate package that verifies assumptions made by summaries in the main spec. Currently contains one rule proving that `liquid_staking::storage::get_sui_amount` is equivalent to `sui_system::staking_pool::get_sui_amount`.

- `assumptions/sources/assumptions.move`:Assumption validation rules.
- `assumptions/Move.toml`:Package manifest.
- `Assumptions.conf`:Prover configuration for running assumption checks.

## Certora Prover

The Certora Prover is a formal verification tool for smart contracts. It statically proves or disproves properties expressed as rules in the Certora Verification Language for Move.

## Running Instructions

0. Install the latest certora prover by following the [installation guide](https://docs.certora.com/en/latest/docs/user-guide/install.html).

1. From the repository root, apply the munges:

```sh
sh certora/munges/munge.sh
```

This only needs to be done once per working copy. **Do not commit the munged files.**

2. Change into the `certora/spec/` directory and run the desired verification job (see table below for all scripts). Example:

```sh
certoraRun confs/solvency.conf
```

Note that `certora/spec/` must be the working directory for `certoraRun`, otherwise it will fail to compile.

3. To revert munges:

```sh
sh certora/munges/unmunge.sh
```

## High-Level Properties

See the doc-comments in each spec file for detailed descriptions of individual rules.

- **Solvency and Exchange Rate Monotonicity** (`solvency.move`, `solvency.conf`, `solvency_monotonicity.conf`): `total_sui_supply >= total_lst_supply` always holds; SUI/LST exchange rate is non-decreasing
- **Total SUI Supply Accounting** (`accounting_total_sui_supply.move`, `accounting_total_sui_supply.conf`): `total_sui_supply` equals the sum of the liquid SUI pool, active stake (via exchange rate), and inactive stake principal
- **Token Supply Initialization Invariants** (`accounting_no_lst_no_sui.move`, `accounting_no_lst_no_sui.conf`): zero LST implies zero SUI backing and vice versa
- **Value Conservation** (`accounting_value_conservation.move`, `accounting_value_conservation.conf`): no value is created or destroyed; on `mint`, deposited SUI equals backing increase plus fees; on `redeem`, SUI decrease equals SUI returned plus fees
- **Fee Accounting Integrity** (`fees.move`, `fees.conf`): accrued fees never exceed total SUI supply, grow monotonically except during collection, and never fully consume a deposit or redemption
- **Supply Control Authorization** (`supply_control.move`, `supply_control_decreases.conf`, `supply_control_increases_p1/p2.conf`): only `mint` can increase supplies; only `redeem`/`custom_redeem` can decrease them
- **No Arbitrage** (`no_arbitrage.move`, `no_arbitrage.conf`): a back-to-back `mint` then `redeem` never yields more SUI than was deposited
- **Validator Registry Consistency** (`validators_consistency.move`, `validators_consistency_p1/p2.conf`): no duplicate validators, registry never exceeds `MAX_VALIDATORS`, no-stake validators have zero `total_sui_amount`
- **Validator Registry Integrity** (`validators_integrity.move`, `validators_integrity.conf`): validators added only by authorized operations, removed only by `refresh`, at most one per call; `refresh` leaves no inactive stake or empty validators
- **Assumption Validation** (`assumptions/sources/assumptions.move`, `Assumptions.conf`): validates assumptions made in summaries

## General Assumptions

- **Loop unrolling.** All specs use `optimistic_loop: true`. `loop_iter` is set to 2 for most validator-list rules and 6 for `validators_upper_bound_step` (one above the munged `MAX_VALIDATORS = 5`).
- **Validator count reduction.** `storage.patch` reduces `MAX_VALIDATORS` from 50 to 5. Several inductive step rules further restrict to 1 validator for tractability.
- **`setup_fresh` precondition.** Most inductive steps model a post-`refresh` state: no inactive stake, all validators have active stake with matching pool IDs and up-to-date exchange rates, `total_sui_supply` is correct, and `last_refresh_epoch == ctx.epoch()`. Each assumption is justified by a separately verified rule.
- **Solvent exchange rates.** `summaries.move` assumes `sui_amount >= pool_token_amount` for all exchange rates, reflecting Sui system-level pool solvency.
- **`redeem_fungible_staked_sui` summary.** The full proportional split between principal and rewards is simplified to `get_sui_amount(er, value)`. An internal `cvlm_assert` checks this is an underapproximation of the actual withdrawal.
13 changes: 13 additions & 0 deletions certora/assumptions/Assumptions.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"optimistic_loop": true,
"prover_args": [
"-maxConcurrentTransforms SIMPLIFIED:1,UNROLL:1,DSA:1",
"-disabledTransformations HOIST_LOOPS",
"-maxMergedBranchSize 1000000",
"-maxCommandCount 10000000",
"-maxBlockCount 1000000",
"-tacDumpsWithInternalFunctions true",
"-callTraceVecElemCount 0",
"-dumpCodeSizeAnalysis true",
]
}
11 changes: 11 additions & 0 deletions certora/assumptions/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "assumptions"
edition = "2024.beta"

[dependencies]
liquid_staking = { local = "../../contracts" }
cvlm = { git = "https://github.com/Certora/cvl-move-proto.git", subdir = "cvlm", rev = "main" }
certora_sui_summaries = { git = "https://github.com/Certora/cvl-move-proto.git", subdir = "certora_sui_summaries", rev = "main" }

[addresses]
assumptions = "0x0"
32 changes: 32 additions & 0 deletions certora/assumptions/sources/assumptions.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module assumptions::assumptions;

/*
Here we validate the assumptions we make in the main spec's summaries
*/

use cvlm::manifest::{rule, function_access};
use cvlm::asserts::cvlm_assert;
use sui_system::staking_pool::PoolTokenExchangeRate;

public fun cvlm_manifest() {
rule(b"get_sui_amount_equivalence");

function_access(b"ls_get_sui_amount", @liquid_staking, b"storage", b"get_sui_amount");
function_access(b"ss_get_sui_amount", @sui_system, b"staking_pool", b"get_sui_amount");
}

// private function accessors
native fun ls_get_sui_amount(exchange_rate: &PoolTokenExchangeRate, token_amount: u64): u64;
native fun ss_get_sui_amount(exchange_rate: &PoolTokenExchangeRate, token_amount: u64): u64;

// The main suilend spec's summaries assume that liquid_staking::storage::get_sui_amount is equivalent to
// sui_system::staking_pool::get_sui_amount. We validate that assumption here.
public fun get_sui_amount_equivalence(
exchange_rate: &PoolTokenExchangeRate,
token_amount: u64
) {
let ls_amount = ls_get_sui_amount(exchange_rate, token_amount);
let ss_amount = ss_get_sui_amount(exchange_rate, token_amount);
cvlm_assert(ls_amount == ss_amount);
}

13 changes: 13 additions & 0 deletions certora/munges/fees.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/contracts/sources/fees.move b/contracts/sources/fees.move
index 33df151..caa341c 100644
--- a/contracts/sources/fees.move
+++ b/contracts/sources/fees.move
@@ -133,7 +133,7 @@ module liquid_staking::fees {
// Note that while it's technically exploitable, we allow lsts to be created with 0 mint/redeem fees.
// This is because having a 0 fee LST is useful in certain cases where mint/redemption can only be done by
// a single party. It is up to the pool creator to ensure that the fees are set correctly.
- fun validate_fees(fees: &FeeConfig) {
+ public fun validate_fees(fees: &FeeConfig) {
assert!(fees.sui_mint_fee_bps <= MAX_BPS, EInvalidFeeConfig);
assert!(fees.staked_sui_mint_fee_bps <= MAX_BPS, EInvalidFeeConfig);
assert!(fees.redeem_fee_bps <= MAX_REDEEM_FEE_BPS, EInvalidFeeConfig);
2 changes: 2 additions & 0 deletions certora/munges/munge.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
git apply -3 certora/munges/fees.patch
git apply -3 certora/munges/storage.patch
2 changes: 2 additions & 0 deletions certora/munges/record_patches.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
git diff HEAD:contracts/sources/fees.move contracts/sources/fees.move > certora/munges/fees.patch;
git diff HEAD:contracts/sources/storage.move contracts/sources/storage.move > certora/munges/storage.patch;
Loading
Loading