123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- import "helpers/helpers.spec"
- methods {
- pushFront(bytes32) envfree
- pushBack(bytes32) envfree
- popFront() returns (bytes32) envfree
- popBack() returns (bytes32) envfree
- clear() envfree
- // exposed for FV
- begin() returns (int128) envfree
- end() returns (int128) envfree
- // view
- length() returns (uint256) envfree
- empty() returns (bool) envfree
- front() returns (bytes32) envfree
- back() returns (bytes32) envfree
- at_(uint256) returns (bytes32) envfree // at is a reserved word
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Helpers │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- function min_int128() returns mathint {
- return -(1 << 127);
- }
- function max_int128() returns mathint {
- return (1 << 127) - 1;
- }
- // Could be broken in theory, but not in practice
- function boundedQueue() returns bool {
- return
- max_int128() > to_mathint(end()) &&
- min_int128() < to_mathint(begin());
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Invariant: end is larger or equal than begin │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- invariant boundariesConsistency()
- end() >= begin()
- filtered { f -> !f.isView }
- { preserved { require boundedQueue(); } }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Invariant: length is end minus begin │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- invariant lengthConsistency()
- length() == to_mathint(end()) - to_mathint(begin())
- filtered { f -> !f.isView }
- { preserved { require boundedQueue(); } }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Invariant: empty() is length 0 and no element exists │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- invariant emptiness()
- empty() <=> length() == 0
- filtered { f -> !f.isView }
- { preserved { require boundedQueue(); } }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Invariant: front points to the first index and back points to the last one │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- invariant queueEndings()
- at_(length() - 1) == back() && at_(0) == front()
- filtered { f -> !f.isView }
- {
- preserved {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- }
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Function correctness: pushFront adds an element at the beginning of the queue │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- rule pushFront(bytes32 value) {
- require boundedQueue();
- uint256 lengthBefore = length();
- pushFront@withrevert(value);
- // liveness
- assert !lastReverted, "never reverts";
- // effect
- assert front() == value, "front set to value";
- assert length() == lengthBefore + 1, "queue extended";
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Rule: pushFront preserves the previous values in the queue with a +1 offset │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- rule pushFrontConsistency(uint256 index) {
- require boundedQueue();
- bytes32 beforeAt = at_(index);
- bytes32 value;
- pushFront(value);
- // try to read value
- bytes32 afterAt = at_@withrevert(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) {
- require boundedQueue();
- uint256 lengthBefore = length();
- pushBack@withrevert(value);
- // liveness
- assert !lastReverted, "never reverts";
- // effect
- assert back() == value, "back set to value";
- assert length() == lengthBefore + 1, "queue increased";
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Rule: pushBack preserves the previous values in the queue │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- rule pushBackConsistency(uint256 index) {
- require boundedQueue();
- 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 {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- 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 => length() == lengthBefore - 1, "queue decreased";
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Rule: at(x) is preserved and offset to at(x - 1) after calling popFront |
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- rule popFrontConsistency(uint256 index) {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- // 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(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 {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- 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 => length() == lengthBefore - 1, "queue decreased";
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Rule: at(x) is preserved after calling popBack |
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- rule popBackConsistency(uint256 index) {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- // Read (any) value that is not the back (this asserts the value exists / the queue is long enough)
- require 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 onlyEmptyRevert(env e) {
- require nonpayable(e);
- requireInvariant boundariesConsistency();
- require boundedQueue();
- method f;
- calldataarg args;
- bool emptyBefore = empty();
- f@withrevert(e, args);
- assert lastReverted => (
- (f.selector == front().selector && emptyBefore) ||
- (f.selector == back().selector && emptyBefore) ||
- (f.selector == popFront().selector && emptyBefore) ||
- (f.selector == popBack().selector && emptyBefore) ||
- f.selector == 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) {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- 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) {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- method f;
- calldataarg args;
- uint256 lengthBefore = length();
- f(e, args);
- uint256 lengthAfter = length();
- assert lengthAfter != lengthBefore => (
- (f.selector == pushFront(bytes32).selector && lengthAfter == lengthBefore + 1) ||
- (f.selector == pushBack(bytes32).selector && lengthAfter == lengthBefore + 1) ||
- (f.selector == popBack().selector && lengthAfter == lengthBefore - 1) ||
- (f.selector == popFront().selector && lengthAfter == lengthBefore - 1) ||
- (f.selector == 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) {
- requireInvariant boundariesConsistency();
- require boundedQueue();
- 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 == clear().selector ||
- (f.selector == popBack().selector && index == length()) ||
- (f.selector == popFront().selector && index == length())
- ), "indexes of the queue are only removed by clear or pop";
- assert atAfterSuccess && atAfter != atBefore => (
- f.selector == popFront().selector ||
- f.selector == pushFront(bytes32).selector
- ), "values of the queue are only changed by popFront or pushFront";
- }
|