dexorder
This commit is contained in:
246
lib_openzeppelin_contracts/certora/specs/EnumerableSet.spec
Normal file
246
lib_openzeppelin_contracts/certora/specs/EnumerableSet.spec
Normal file
@@ -0,0 +1,246 @@
|
||||
import "helpers/helpers.spec";
|
||||
|
||||
methods {
|
||||
// library
|
||||
function add(bytes32) external returns (bool) envfree;
|
||||
function remove(bytes32) external returns (bool) envfree;
|
||||
function contains(bytes32) external returns (bool) envfree;
|
||||
function length() external returns (uint256) envfree;
|
||||
function at_(uint256) external returns (bytes32) envfree;
|
||||
|
||||
// FV
|
||||
function _positionOf(bytes32) external returns (uint256) envfree;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
definition sanity() returns bool =
|
||||
length() < max_uint256;
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: All indexed keys are contained │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant indexedContained(uint256 index)
|
||||
index < length() => contains(at_(index))
|
||||
{
|
||||
preserved {
|
||||
requireInvariant consistencyIndex(index);
|
||||
requireInvariant consistencyIndex(require_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: A value can only be stored at a single location │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant atUniqueness(uint256 index1, uint256 index2)
|
||||
index1 == index2 <=> at_(index1) == at_(index2)
|
||||
{
|
||||
preserved remove(bytes32 key) {
|
||||
requireInvariant atUniqueness(index1, require_uint256(length() - 1));
|
||||
requireInvariant atUniqueness(index2, require_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: index <> key relationship is consistent │
|
||||
│ │
|
||||
│ Note that the two consistencyXxx invariants, put together, prove that at_ and _positionOf are inverse of one │
|
||||
│ another. This proves that we have a bijection between indices (the enumerability part) and keys (the entries that │
|
||||
│ are added and removed from the EnumerableSet). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant consistencyIndex(uint256 index)
|
||||
index < length() => _positionOf(at_(index)) == require_uint256(index + 1)
|
||||
{
|
||||
preserved remove(bytes32 key) {
|
||||
requireInvariant consistencyIndex(require_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
invariant consistencyKey(bytes32 key)
|
||||
contains(key) => (
|
||||
_positionOf(key) > 0 &&
|
||||
_positionOf(key) <= length() &&
|
||||
at_(require_uint256(_positionOf(key) - 1)) == key
|
||||
)
|
||||
{
|
||||
preserved remove(bytes32 otherKey) {
|
||||
requireInvariant consistencyKey(otherKey);
|
||||
requireInvariant atUniqueness(
|
||||
require_uint256(_positionOf(key) - 1),
|
||||
require_uint256(_positionOf(otherKey) - 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: state only changes by adding or removing elements │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule stateChange(env e, bytes32 key) {
|
||||
require sanity();
|
||||
requireInvariant consistencyKey(key);
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
f(e, args);
|
||||
|
||||
uint256 lengthAfter = length();
|
||||
bool containsAfter = contains(key);
|
||||
|
||||
assert lengthBefore != lengthAfter => (
|
||||
(f.selector == sig:add(bytes32).selector && lengthAfter == require_uint256(lengthBefore + 1)) ||
|
||||
(f.selector == sig:remove(bytes32).selector && lengthAfter == require_uint256(lengthBefore - 1))
|
||||
);
|
||||
|
||||
assert containsBefore != containsAfter => (
|
||||
(f.selector == sig:add(bytes32).selector && containsAfter) ||
|
||||
(f.selector == sig:remove(bytes32).selector && containsBefore)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: check liveness of view functions. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule liveness_1(bytes32 key) {
|
||||
requireInvariant consistencyKey(key);
|
||||
|
||||
// contains never revert
|
||||
contains@withrevert(key);
|
||||
assert !lastReverted;
|
||||
}
|
||||
|
||||
rule liveness_2(uint256 index) {
|
||||
requireInvariant consistencyIndex(index);
|
||||
|
||||
// length never revert
|
||||
uint256 length = length@withrevert();
|
||||
assert !lastReverted;
|
||||
|
||||
// at reverts iff the index is out of bound
|
||||
at_@withrevert(index);
|
||||
assert !lastReverted <=> index < length;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: add key to EnumerableSet if not already contained │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule add(bytes32 key, bytes32 otherKey) {
|
||||
require sanity();
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
bool containsOtherBefore = contains(otherKey);
|
||||
|
||||
bool added = add@withrevert(key);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success && contains(key),
|
||||
"liveness & immediate effect";
|
||||
|
||||
assert added <=> !containsBefore,
|
||||
"return value: added iff not contained";
|
||||
|
||||
assert length() == require_uint256(lengthBefore + to_mathint(added ? 1 : 0)),
|
||||
"effect: length increases iff added";
|
||||
|
||||
assert added => at_(lengthBefore) == key,
|
||||
"effect: add at the end";
|
||||
|
||||
assert containsOtherBefore != contains(otherKey) => (added && key == otherKey),
|
||||
"side effect: other keys are not affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: remove key from EnumerableSet if already contained │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule remove(bytes32 key, bytes32 otherKey) {
|
||||
requireInvariant consistencyKey(key);
|
||||
requireInvariant consistencyKey(otherKey);
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
bool containsOtherBefore = contains(otherKey);
|
||||
|
||||
bool removed = remove@withrevert(key);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success && !contains(key),
|
||||
"liveness & immediate effect";
|
||||
|
||||
assert removed <=> containsBefore,
|
||||
"return value: removed iff contained";
|
||||
|
||||
assert length() == require_uint256(lengthBefore - to_mathint(removed ? 1 : 0)),
|
||||
"effect: length decreases iff removed";
|
||||
|
||||
assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey),
|
||||
"side effect: other keys are not affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: when adding a new key, the other keys remain in set, at the same index. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule addEnumerability(bytes32 key, uint256 index) {
|
||||
require sanity();
|
||||
|
||||
bytes32 atBefore = at_(index);
|
||||
add(key);
|
||||
bytes32 atAfter = at_@withrevert(index);
|
||||
bool atAfterSuccess = !lastReverted;
|
||||
|
||||
assert atAfterSuccess;
|
||||
assert atBefore == atAfter;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule removeEnumerability(bytes32 key, uint256 index) {
|
||||
uint256 last = require_uint256(length() - 1);
|
||||
|
||||
requireInvariant consistencyKey(key);
|
||||
requireInvariant consistencyIndex(index);
|
||||
requireInvariant consistencyIndex(last);
|
||||
|
||||
bytes32 atBefore = at_(index);
|
||||
bytes32 lastBefore = at_(last);
|
||||
|
||||
remove(key);
|
||||
|
||||
// can't read last value (length decreased)
|
||||
bytes32 atAfter = at_@withrevert(index);
|
||||
assert lastReverted <=> index == last;
|
||||
|
||||
// One value that is allowed to change is if previous value was removed,
|
||||
// in that case the last value before took its place.
|
||||
assert (
|
||||
index != last &&
|
||||
atBefore != atAfter
|
||||
) => (
|
||||
atBefore == key &&
|
||||
atAfter == lastBefore
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user