You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
19 KiB
300 lines
19 KiB
import "helpers/helpers.spec";
|
|
|
|
methods {
|
|
function pushFront(bytes32) external envfree;
|
|
function pushBack(bytes32) external envfree;
|
|
function popFront() external returns (bytes32) envfree;
|
|
function popBack() external returns (bytes32) envfree;
|
|
function clear() external envfree;
|
|
|
|
// exposed for FV
|
|
function begin() external returns (uint128) envfree;
|
|
function end() external returns (uint128) envfree;
|
|
|
|
// view
|
|
function length() external returns (uint256) envfree;
|
|
function empty() external returns (bool) envfree;
|
|
function front() external returns (bytes32) envfree;
|
|
function back() external returns (bytes32) envfree;
|
|
function at_(uint256) external returns (bytes32) envfree; // at is a reserved word
|
|
}
|
|
|
|
definition full() returns bool = length() == max_uint128;
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Invariant: empty() is length 0 and no element exists │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
invariant emptiness()
|
|
empty() <=> length() == 0
|
|
filtered { f -> !f.isView }
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Invariant: front points to the first index and back points to the last one │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
invariant queueFront()
|
|
at_(0) == front()
|
|
filtered { f -> !f.isView }
|
|
|
|
invariant queueBack()
|
|
at_(require_uint256(length() - 1)) == back()
|
|
filtered { f -> !f.isView }
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Function correctness: pushFront adds an element at the beginning of the queue │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule pushFront(bytes32 value) {
|
|
uint256 lengthBefore = length();
|
|
bool fullBefore = full();
|
|
|
|
pushFront@withrevert(value);
|
|
bool success = !lastReverted;
|
|
|
|
// liveness
|
|
assert success <=> !fullBefore, "never revert if not previously full";
|
|
|
|
// effect
|
|
assert success => front() == value, "front set to value";
|
|
assert success => to_mathint(length()) == lengthBefore + 1, "queue extended";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: pushFront preserves the previous values in the queue with a +1 offset │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule pushFrontConsistency(uint256 index) {
|
|
bytes32 beforeAt = at_(index);
|
|
|
|
bytes32 value;
|
|
pushFront(value);
|
|
|
|
// try to read value
|
|
bytes32 afterAt = at_@withrevert(require_uint256(index + 1));
|
|
|
|
assert !lastReverted, "value still there";
|
|
assert afterAt == beforeAt, "data is preserved";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Function correctness: pushBack adds an element at the end of the queue │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule pushBack(bytes32 value) {
|
|
uint256 lengthBefore = length();
|
|
bool fullBefore = full();
|
|
|
|
pushBack@withrevert(value);
|
|
bool success = !lastReverted;
|
|
|
|
// liveness
|
|
assert success <=> !fullBefore, "never revert if not previously full";
|
|
|
|
// effect
|
|
assert success => back() == value, "back set to value";
|
|
assert success => to_mathint(length()) == lengthBefore + 1, "queue increased";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: pushBack preserves the previous values in the queue │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule pushBackConsistency(uint256 index) {
|
|
bytes32 beforeAt = at_(index);
|
|
|
|
bytes32 value;
|
|
pushBack(value);
|
|
|
|
// try to read value
|
|
bytes32 afterAt = at_@withrevert(index);
|
|
|
|
assert !lastReverted, "value still there";
|
|
assert afterAt == beforeAt, "data is preserved";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Function correctness: popFront removes an element from the beginning of the queue │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule popFront {
|
|
uint256 lengthBefore = length();
|
|
bytes32 frontBefore = front@withrevert();
|
|
|
|
bytes32 popped = popFront@withrevert();
|
|
bool success = !lastReverted;
|
|
|
|
// liveness
|
|
assert success <=> lengthBefore != 0, "never reverts if not previously empty";
|
|
|
|
// effect
|
|
assert success => frontBefore == popped, "previous front is returned";
|
|
assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: at(x) is preserved and offset to at(x - 1) after calling popFront |
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule popFrontConsistency(uint256 index) {
|
|
// Read (any) value that is not the front (this asserts the value exists / the queue is long enough)
|
|
require index > 1;
|
|
bytes32 before = at_(index);
|
|
|
|
popFront();
|
|
|
|
// try to read value
|
|
bytes32 after = at_@withrevert(require_uint256(index - 1));
|
|
|
|
assert !lastReverted, "value still exists in the queue";
|
|
assert before == after, "values are offset and not modified";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Function correctness: popBack removes an element from the end of the queue │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule popBack {
|
|
uint256 lengthBefore = length();
|
|
bytes32 backBefore = back@withrevert();
|
|
|
|
bytes32 popped = popBack@withrevert();
|
|
bool success = !lastReverted;
|
|
|
|
// liveness
|
|
assert success <=> lengthBefore != 0, "never reverts if not previously empty";
|
|
|
|
// effect
|
|
assert success => backBefore == popped, "previous back is returned";
|
|
assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: at(x) is preserved after calling popBack |
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule popBackConsistency(uint256 index) {
|
|
// Read (any) value that is not the back (this asserts the value exists / the queue is long enough)
|
|
require to_mathint(index) < length() - 1;
|
|
bytes32 before = at_(index);
|
|
|
|
popBack();
|
|
|
|
// try to read value
|
|
bytes32 after = at_@withrevert(index);
|
|
|
|
assert !lastReverted, "value still exists in the queue";
|
|
assert before == after, "values are offset and not modified";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Function correctness: clear sets length to 0 │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule clear {
|
|
clear@withrevert();
|
|
|
|
// liveness
|
|
assert !lastReverted, "never reverts";
|
|
|
|
// effect
|
|
assert length() == 0, "sets length to 0";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: front/back access reverts only if the queue is empty or querying out of bounds │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule onlyEmptyOrFullRevert(env e) {
|
|
require nonpayable(e);
|
|
|
|
method f;
|
|
calldataarg args;
|
|
|
|
bool emptyBefore = empty();
|
|
bool fullBefore = full();
|
|
|
|
f@withrevert(e, args);
|
|
|
|
assert lastReverted => (
|
|
(f.selector == sig:front().selector && emptyBefore) ||
|
|
(f.selector == sig:back().selector && emptyBefore) ||
|
|
(f.selector == sig:popFront().selector && emptyBefore) ||
|
|
(f.selector == sig:popBack().selector && emptyBefore) ||
|
|
(f.selector == sig:pushFront(bytes32).selector && fullBefore ) ||
|
|
(f.selector == sig:pushBack(bytes32).selector && fullBefore ) ||
|
|
f.selector == sig:at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert
|
|
), "only revert if empty or out of bounds";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: at(index) only reverts if index is out of bounds |
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule onlyOutOfBoundsRevert(uint256 index) {
|
|
at_@withrevert(index);
|
|
|
|
assert lastReverted <=> index >= length(), "only reverts if index is out of bounds";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: only clear/push/pop operations can change the length of the queue │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule noLengthChange(env e) {
|
|
method f;
|
|
calldataarg args;
|
|
|
|
uint256 lengthBefore = length();
|
|
f(e, args);
|
|
uint256 lengthAfter = length();
|
|
|
|
assert lengthAfter != lengthBefore => (
|
|
(f.selector == sig:pushFront(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) ||
|
|
(f.selector == sig:pushBack(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) ||
|
|
(f.selector == sig:popBack().selector && to_mathint(lengthAfter) == lengthBefore - 1) ||
|
|
(f.selector == sig:popFront().selector && to_mathint(lengthAfter) == lengthBefore - 1) ||
|
|
(f.selector == sig:clear().selector && lengthAfter == 0)
|
|
), "length is only affected by clear/pop/push operations";
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: only push/pop can change values bounded in the queue (outside values aren't cleared) │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule noDataChange(env e) {
|
|
method f;
|
|
calldataarg args;
|
|
|
|
uint256 index;
|
|
bytes32 atBefore = at_(index);
|
|
f(e, args);
|
|
bytes32 atAfter = at_@withrevert(index);
|
|
bool atAfterSuccess = !lastReverted;
|
|
|
|
assert !atAfterSuccess <=> (
|
|
(f.selector == sig:clear().selector ) ||
|
|
(f.selector == sig:popBack().selector && index == length()) ||
|
|
(f.selector == sig:popFront().selector && index == length())
|
|
), "indexes of the queue are only removed by clear or pop";
|
|
|
|
assert atAfterSuccess && atAfter != atBefore => (
|
|
f.selector == sig:popFront().selector ||
|
|
f.selector == sig:pushFront(bytes32).selector
|
|
), "values of the queue are only changed by popFront or pushFront";
|
|
}
|
|
|