Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Certora] Delegation invariant #102

Merged
merged 26 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7fff52f
test forall
colin-morpho Nov 21, 2024
946b9c9
test partial sums
colin-morpho Nov 22, 2024
eccc1f0
feat: voting power correct by parametricity
colin-morpho Nov 25, 2024
a11bb33
fix: change setup to have verification go through
colin-morpho Nov 29, 2024
4fff66f
Merge branch 'colin@verif/delegation' into colin@verif/delegation-inv…
colin-morpho Nov 29, 2024
28ce6f8
fix: update readme
colin-morpho Nov 29, 2024
1c593dc
feat: prove the sum of two balances is not greater than supply
colin-morpho Dec 2, 2024
9de63d8
chore: formatting
colin-morpho Dec 2, 2024
bf3c683
Merge branch 'colin@verif/delegation' into colin@verif/delegation-inv…
colin-morpho Dec 2, 2024
f020729
chore: push parent's commits
colin-morpho Dec 2, 2024
e7688d2
fix: change total virtual voting power to invariant
colin-morpho Dec 2, 2024
0b63d66
Merge branch 'colin@verif/delegation' into colin@verif/delegation-inv…
colin-morpho Dec 2, 2024
a2d1a20
Apply suggestions from code review
colin-morpho Dec 4, 2024
faacfc3
fix: implement reviewsuggestions
colin-morpho Dec 4, 2024
b6dbaec
chore: add config for invariants
colin-morpho Dec 4, 2024
894edaf
chore: update readme
colin-morpho Dec 4, 2024
1d4b5c2
chore: update ci config
colin-morpho Dec 4, 2024
675c524
fix: typo in ci config
colin-morpho Dec 4, 2024
564cf72
fix: typo in conf
colin-morpho Dec 4, 2024
b888a1f
Apply suggestions from code review
colin-morpho Dec 4, 2024
fc5b6cb
fix: typo in spec
colin-morpho Dec 4, 2024
15a21f1
chore: apply suggestions from code review
colin-morpho Dec 5, 2024
ecf4070
docs: improve docs
colin-morpho Dec 6, 2024
226a313
docs: apply suggestions from code review
colin-morpho Dec 6, 2024
c263e91
docs: apply suggestions from code review
colin-morpho Dec 6, 2024
58c9a1a
Merge branch 'main' into colin@verif/delegation-invariants
colin-morpho Dec 6, 2024
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
20 changes: 14 additions & 6 deletions certora/specs/Delegation.spec
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ methods {
}

// Ghost variable to hold the sum of voting power.
// To reason exhaustively on the value of of delegated voting power we proceed to compute the partial sum of balances for each possible address.
// We call the partial sum of votes to parameter A up to an addrress a, to sum of delegated votes to parameter A for all addresses within the range [0..a[.
// Formally, we write ∀ a:address → sumsOfVotesDelegatedToA[succ a] = Σᵢ₌₀ᵃ delegatee(a) = A ⇒ balanceOf(i), provided that the address zero holds no voting power and that it never performs transactions.
// With this approach, we are able to write and check more abstract properties about the computation of the total delegated voting power using universal quantifiers.
// From this follows the property such that, ∀ a:address, delegatee(a) = A ⇒ balanceOf(a) ≤ delegatedVotingPower(A), which can be proven using the fact the sumsOfVotesDelegatedToA is monotonic and that the sum of voting power delegated to A grows steadily on each successive address.
// In particular, we have the equality sumsOfVotesDelegatedToA[2^160] = delegatedVotingPower(A).
// Finally, we reason by parametricity to observe since we have ∀ a:address, delegatee(a) = A ⇒ balanceOf(a) ≤ delegatedVotingPower(A)∀ a:address, delegatee(a) = A ⇒ balanceOf(a) ≤ delegatedVotingPower(A),
// we also have ∀ A : address, ∀ a:address, A ≠ 0 ∧ delegatee(a) = A ⇒ balanceOf(a) ≤ delegatedVotingPower(A), which is what we want to show.
ghost mathint sumOfVotingPower {
init_state axiom sumOfVotingPower == 0;
}
Expand Down Expand Up @@ -35,14 +43,14 @@ invariant zeroAddressNoVotingPower()

// Check that initially zero votes are delegated to parameterized address A.
invariant sumOfVotesStartsAtZero()
sumsOfVotes[0] == 0;
sumsOfVotesDelegatedToA[0] == 0;

invariant sumOfVotesGrowsCorrectly()
forall address account. sumsOfVotes[to_mathint(account) + 1] ==
sumsOfVotes[to_mathint(account)] + (ghostDelegatee[account] == A ? ghostBalances[account] : 0) ;
forall address account. sumsOfVotesDelegatedToA[to_mathint(account) + 1] ==
sumsOfVotesDelegatedToA[to_mathint(account)] + (ghostDelegatee[account] == A ? ghostBalances[account] : 0) ;

invariant sumOfVotesMonotone()
forall mathint i. forall mathint j. i <= j => sumsOfVotes[i] <= sumsOfVotes[j]
forall mathint i. forall mathint j. i <= j => sumsOfVotesDelegatedToA[i] <= sumsOfVotesDelegatedToA[j]
{
preserved {
requireInvariant sumOfVotesStartsAtZero();
Expand All @@ -52,7 +60,7 @@ invariant sumOfVotesMonotone()

invariant delegatedLTEqPartialSum()
forall address account. ghostDelegatee[account] == A =>
ghostBalances[account] <= sumsOfVotes[to_mathint(account)+1]
ghostBalances[account] <= sumsOfVotesDelegatedToA[to_mathint(account)+1]
{
preserved {
requireInvariant sumOfVotesStartsAtZero();
Expand All @@ -63,7 +71,7 @@ invariant delegatedLTEqPartialSum()


invariant sumOfVotesIsDelegatedToA()
sumsOfVotes[2^160] == ghostDelegatedVotingPower[A]
sumsOfVotesDelegatedToA[2^160] == ghostDelegatedVotingPower[A]
{
preserved {
requireInvariant zeroAddressNoVotingPower();
Expand Down
24 changes: 15 additions & 9 deletions certora/specs/ERC20Invariants.spec
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ persistent ghost address A {

// Partial sum of delegated votes to parameterized address A.
// sumOfvotes[x] = \sum_{i=0}^{x-1} balances[i] when delegatee[i] == A;
ghost mapping(mathint => mathint) sumsOfVotes {
init_state axiom forall mathint account. sumsOfVotes[account] == 0;
ghost mapping(mathint => mathint) sumsOfVotesDelegatedToA {
init_state axiom forall mathint account. sumsOfVotesDelegatedToA[account] == 0;
}

// Ghost copy of DelegationTokenStorage._delegatee for quantification.
Expand All @@ -35,21 +35,27 @@ hook Sstore (slot 0x669be2f4ee1b0b5f3858e4135f31064efe8fa923b09bf21bf538f64f2c3e
// Track delegation changes from the parameterized address.
if (delegateeOld == A && delegatee != A) {
require changeOfAccountVotesForA == - ghostBalances[account];
// Track delegation changes to the pramaeterized address.
// Track delegation changes to the prameterized address.
} else if (delegateeOld != A && delegatee == A) {
require changeOfAccountVotesForA == ghostBalances[account];
} else {
require changeOfAccountVotesForA == 0;
}
// Update partial sums for x > to_mathint(account)
havoc sumsOfVotes assuming
forall mathint x. sumsOfVotes@new[x] ==
sumsOfVotes@old[x] + (to_mathint(account) < x ? changeOfAccountVotesForA : 0);
havoc sumsOfVotesDelegatedToA assuming
forall mathint x. sumsOfVotesDelegatedToA@new[x] ==
sumsOfVotesDelegatedToA@old[x] + (to_mathint(account) < x ? changeOfAccountVotesForA : 0);
// Update ghost copy of DelegationTokenStorage._delegatee.
ghostDelegatee[account] = delegatee;
}

// Ghost variable to hold the sum of balances.
// To reason exhaustively on the value of the sum of balances we proceed to compute the partial sum of balances for each possible address.
// We call the partial sum of balances up to an addrress a, to sum of balances for all addresses within the range [0..a[.
// Formally, we write ∀ a:address → sumOfBalances[succ a] = Σᵢ₌₀ᵃ balanceOf(i), provided that the address zero holds no token and that it never performs transactions.
// With this approach, we are able to write and check more abstract properties about the computation of the total supply of tokens using universal quantifiers.
// From this follows the property such that, ∀ a:address, balanceOf(a) ≤ totalSupply(), which can be proven using the fact the sumOfBalances is monotonic and that the sum of balances grows steadily on each successive address.
// In particular we have the equality, sumOfBalances[2^160] = totalSupply() and we are able to to show that the sum of two different balances is lesser than or equal to the total supply.
ghost mapping(mathint => mathint) sumOfBalances {
init_state axiom forall mathint addr. sumOfBalances[addr] == 0;
}
Expand All @@ -74,9 +80,9 @@ hook Sstore (slot 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20ba
// Update partial sums of votes delegated to the parameterized address, for x > to_mathint(account)
// Track balance changes when the delegatee is the parameterized address.
if (ghostDelegatee[account] == A) {
havoc sumsOfVotes assuming
forall mathint x. sumsOfVotes@new[x] ==
sumsOfVotes@old[x] + (to_mathint(account) < x ? newValue - oldValue : 0);
havoc sumsOfVotesDelegatedToA assuming
forall mathint x. sumsOfVotesDelegatedToA@new[x] ==
sumsOfVotesDelegatedToA@old[x] + (to_mathint(account) < x ? newValue - oldValue : 0);
}
// Update ghost copy of ERC20Storage._balances.
ghostBalances[account] = newValue;
Expand Down
Loading