DoubleEndedQueue.spec 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import "helpers/helpers.spec"
  2. methods {
  3. pushFront(bytes32) envfree
  4. pushBack(bytes32) envfree
  5. popFront() returns (bytes32) envfree
  6. popBack() returns (bytes32) envfree
  7. clear() envfree
  8. // exposed for FV
  9. begin() returns (int128) envfree
  10. end() returns (int128) envfree
  11. // view
  12. length() returns (uint256) envfree
  13. empty() returns (bool) envfree
  14. front() returns (bytes32) envfree
  15. back() returns (bytes32) envfree
  16. at_(uint256) returns (bytes32) envfree // at is a reserved word
  17. }
  18. /*
  19. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  20. │ Helpers │
  21. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  22. */
  23. function min_int128() returns mathint {
  24. return -(1 << 127);
  25. }
  26. function max_int128() returns mathint {
  27. return (1 << 127) - 1;
  28. }
  29. // Could be broken in theory, but not in practice
  30. function boundedQueue() returns bool {
  31. return
  32. max_int128() > to_mathint(end()) &&
  33. min_int128() < to_mathint(begin());
  34. }
  35. /*
  36. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  37. │ Invariant: end is larger or equal than begin │
  38. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  39. */
  40. invariant boundariesConsistency()
  41. end() >= begin()
  42. filtered { f -> !f.isView }
  43. { preserved { require boundedQueue(); } }
  44. /*
  45. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  46. │ Invariant: length is end minus begin │
  47. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  48. */
  49. invariant lengthConsistency()
  50. length() == to_mathint(end()) - to_mathint(begin())
  51. filtered { f -> !f.isView }
  52. { preserved { require boundedQueue(); } }
  53. /*
  54. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  55. │ Invariant: empty() is length 0 and no element exists │
  56. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  57. */
  58. invariant emptiness()
  59. empty() <=> length() == 0
  60. filtered { f -> !f.isView }
  61. { preserved { require boundedQueue(); } }
  62. /*
  63. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  64. │ Invariant: front points to the first index and back points to the last one │
  65. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  66. */
  67. invariant queueEndings()
  68. at_(length() - 1) == back() && at_(0) == front()
  69. filtered { f -> !f.isView }
  70. {
  71. preserved {
  72. requireInvariant boundariesConsistency();
  73. require boundedQueue();
  74. }
  75. }
  76. /*
  77. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  78. │ Function correctness: pushFront adds an element at the beginning of the queue │
  79. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  80. */
  81. rule pushFront(bytes32 value) {
  82. require boundedQueue();
  83. uint256 lengthBefore = length();
  84. pushFront@withrevert(value);
  85. // liveness
  86. assert !lastReverted, "never reverts";
  87. // effect
  88. assert front() == value, "front set to value";
  89. assert length() == lengthBefore + 1, "queue extended";
  90. }
  91. /*
  92. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  93. │ Rule: pushFront preserves the previous values in the queue with a +1 offset │
  94. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  95. */
  96. rule pushFrontConsistency(uint256 index) {
  97. require boundedQueue();
  98. bytes32 beforeAt = at_(index);
  99. bytes32 value;
  100. pushFront(value);
  101. // try to read value
  102. bytes32 afterAt = at_@withrevert(index + 1);
  103. assert !lastReverted, "value still there";
  104. assert afterAt == beforeAt, "data is preserved";
  105. }
  106. /*
  107. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  108. │ Function correctness: pushBack adds an element at the end of the queue │
  109. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  110. */
  111. rule pushBack(bytes32 value) {
  112. require boundedQueue();
  113. uint256 lengthBefore = length();
  114. pushBack@withrevert(value);
  115. // liveness
  116. assert !lastReverted, "never reverts";
  117. // effect
  118. assert back() == value, "back set to value";
  119. assert length() == lengthBefore + 1, "queue increased";
  120. }
  121. /*
  122. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  123. │ Rule: pushBack preserves the previous values in the queue │
  124. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  125. */
  126. rule pushBackConsistency(uint256 index) {
  127. require boundedQueue();
  128. bytes32 beforeAt = at_(index);
  129. bytes32 value;
  130. pushBack(value);
  131. // try to read value
  132. bytes32 afterAt = at_@withrevert(index);
  133. assert !lastReverted, "value still there";
  134. assert afterAt == beforeAt, "data is preserved";
  135. }
  136. /*
  137. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  138. │ Function correctness: popFront removes an element from the beginning of the queue │
  139. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  140. */
  141. rule popFront {
  142. requireInvariant boundariesConsistency();
  143. require boundedQueue();
  144. uint256 lengthBefore = length();
  145. bytes32 frontBefore = front@withrevert();
  146. bytes32 popped = popFront@withrevert();
  147. bool success = !lastReverted;
  148. // liveness
  149. assert success <=> lengthBefore != 0, "never reverts if not previously empty";
  150. // effect
  151. assert success => frontBefore == popped, "previous front is returned";
  152. assert success => length() == lengthBefore - 1, "queue decreased";
  153. }
  154. /*
  155. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  156. │ Rule: at(x) is preserved and offset to at(x - 1) after calling popFront |
  157. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  158. */
  159. rule popFrontConsistency(uint256 index) {
  160. requireInvariant boundariesConsistency();
  161. require boundedQueue();
  162. // Read (any) value that is not the front (this asserts the value exists / the queue is long enough)
  163. require index > 1;
  164. bytes32 before = at_(index);
  165. popFront();
  166. // try to read value
  167. bytes32 after = at_@withrevert(index - 1);
  168. assert !lastReverted, "value still exists in the queue";
  169. assert before == after, "values are offset and not modified";
  170. }
  171. /*
  172. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  173. │ Function correctness: popBack removes an element from the end of the queue │
  174. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  175. */
  176. rule popBack {
  177. requireInvariant boundariesConsistency();
  178. require boundedQueue();
  179. uint256 lengthBefore = length();
  180. bytes32 backBefore = back@withrevert();
  181. bytes32 popped = popBack@withrevert();
  182. bool success = !lastReverted;
  183. // liveness
  184. assert success <=> lengthBefore != 0, "never reverts if not previously empty";
  185. // effect
  186. assert success => backBefore == popped, "previous back is returned";
  187. assert success => length() == lengthBefore - 1, "queue decreased";
  188. }
  189. /*
  190. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  191. │ Rule: at(x) is preserved after calling popBack |
  192. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  193. */
  194. rule popBackConsistency(uint256 index) {
  195. requireInvariant boundariesConsistency();
  196. require boundedQueue();
  197. // Read (any) value that is not the back (this asserts the value exists / the queue is long enough)
  198. require index < length() - 1;
  199. bytes32 before = at_(index);
  200. popBack();
  201. // try to read value
  202. bytes32 after = at_@withrevert(index);
  203. assert !lastReverted, "value still exists in the queue";
  204. assert before == after, "values are offset and not modified";
  205. }
  206. /*
  207. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  208. │ Function correctness: clear sets length to 0 │
  209. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  210. */
  211. rule clear {
  212. clear@withrevert();
  213. // liveness
  214. assert !lastReverted, "never reverts";
  215. // effect
  216. assert length() == 0, "sets length to 0";
  217. }
  218. /*
  219. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  220. │ Rule: front/back access reverts only if the queue is empty or querying out of bounds │
  221. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  222. */
  223. rule onlyEmptyRevert(env e) {
  224. require nonpayable(e);
  225. requireInvariant boundariesConsistency();
  226. require boundedQueue();
  227. method f;
  228. calldataarg args;
  229. bool emptyBefore = empty();
  230. f@withrevert(e, args);
  231. assert lastReverted => (
  232. (f.selector == front().selector && emptyBefore) ||
  233. (f.selector == back().selector && emptyBefore) ||
  234. (f.selector == popFront().selector && emptyBefore) ||
  235. (f.selector == popBack().selector && emptyBefore) ||
  236. f.selector == at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert
  237. ), "only revert if empty or out of bounds";
  238. }
  239. /*
  240. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  241. │ Rule: at(index) only reverts if index is out of bounds |
  242. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  243. */
  244. rule onlyOutOfBoundsRevert(uint256 index) {
  245. requireInvariant boundariesConsistency();
  246. require boundedQueue();
  247. at_@withrevert(index);
  248. assert lastReverted <=> index >= length(), "only reverts if index is out of bounds";
  249. }
  250. /*
  251. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  252. │ Rule: only clear/push/pop operations can change the length of the queue │
  253. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  254. */
  255. rule noLengthChange(env e) {
  256. requireInvariant boundariesConsistency();
  257. require boundedQueue();
  258. method f;
  259. calldataarg args;
  260. uint256 lengthBefore = length();
  261. f(e, args);
  262. uint256 lengthAfter = length();
  263. assert lengthAfter != lengthBefore => (
  264. (f.selector == pushFront(bytes32).selector && lengthAfter == lengthBefore + 1) ||
  265. (f.selector == pushBack(bytes32).selector && lengthAfter == lengthBefore + 1) ||
  266. (f.selector == popBack().selector && lengthAfter == lengthBefore - 1) ||
  267. (f.selector == popFront().selector && lengthAfter == lengthBefore - 1) ||
  268. (f.selector == clear().selector && lengthAfter == 0)
  269. ), "length is only affected by clear/pop/push operations";
  270. }
  271. /*
  272. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  273. │ Rule: only push/pop can change values bounded in the queue (outside values aren't cleared) │
  274. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  275. */
  276. rule noDataChange(env e) {
  277. requireInvariant boundariesConsistency();
  278. require boundedQueue();
  279. method f;
  280. calldataarg args;
  281. uint256 index;
  282. bytes32 atBefore = at_(index);
  283. f(e, args);
  284. bytes32 atAfter = at_@withrevert(index);
  285. bool atAfterSuccess = !lastReverted;
  286. assert !atAfterSuccess <=> (
  287. f.selector == clear().selector ||
  288. (f.selector == popBack().selector && index == length()) ||
  289. (f.selector == popFront().selector && index == length())
  290. ), "indexes of the queue are only removed by clear or pop";
  291. assert atAfterSuccess && atAfter != atBefore => (
  292. f.selector == popFront().selector ||
  293. f.selector == pushFront(bytes32).selector
  294. ), "values of the queue are only changed by popFront or pushFront";
  295. }