EnumerableSet.spec 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import "helpers/helpers.spec";
  2. methods {
  3. // library
  4. function add(bytes32) external returns (bool) envfree;
  5. function remove(bytes32) external returns (bool) envfree;
  6. function contains(bytes32) external returns (bool) envfree;
  7. function length() external returns (uint256) envfree;
  8. function at_(uint256) external returns (bytes32) envfree;
  9. // FV
  10. function _positionOf(bytes32) external returns (uint256) envfree;
  11. }
  12. /*
  13. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  14. │ Helpers │
  15. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  16. */
  17. definition lengthSanity() returns bool =
  18. length() < max_uint256;
  19. /*
  20. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  21. │ Invariant: All indexed keys are contained │
  22. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  23. */
  24. invariant indexedContained(uint256 index)
  25. index < length() => contains(at_(index))
  26. {
  27. preserved {
  28. requireInvariant consistencyIndex(index);
  29. requireInvariant consistencyIndex(require_uint256(length() - 1));
  30. }
  31. }
  32. /*
  33. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  34. │ Invariant: A value can only be stored at a single location │
  35. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  36. */
  37. invariant atUniqueness(uint256 index1, uint256 index2)
  38. (index1 < length() && index2 < length()) =>
  39. (index1 == index2 <=> at_(index1) == at_(index2))
  40. {
  41. preserved {
  42. requireInvariant consistencyIndex(index1);
  43. requireInvariant consistencyIndex(index2);
  44. }
  45. preserved remove(bytes32 key) {
  46. requireInvariant atUniqueness(index1, require_uint256(length() - 1));
  47. requireInvariant atUniqueness(index2, require_uint256(length() - 1));
  48. }
  49. }
  50. /*
  51. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  52. │ Invariant: index <> key relationship is consistent │
  53. │ │
  54. │ Note that the two consistencyXxx invariants, put together, prove that at_ and _positionOf are inverse of one │
  55. │ another. This proves that we have a bijection between indices (the enumerability part) and keys (the entries that │
  56. │ are added and removed from the EnumerableSet). │
  57. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  58. */
  59. invariant consistencyIndex(uint256 index)
  60. index < length() => _positionOf(at_(index)) == require_uint256(index + 1)
  61. {
  62. preserved remove(bytes32 key) {
  63. requireInvariant consistencyIndex(require_uint256(length() - 1));
  64. }
  65. }
  66. invariant consistencyKey(bytes32 key)
  67. contains(key) => (
  68. _positionOf(key) > 0 &&
  69. _positionOf(key) <= length() &&
  70. at_(require_uint256(_positionOf(key) - 1)) == key
  71. )
  72. {
  73. preserved {
  74. require lengthSanity();
  75. }
  76. preserved remove(bytes32 otherKey) {
  77. requireInvariant consistencyKey(otherKey);
  78. requireInvariant atUniqueness(
  79. require_uint256(_positionOf(key) - 1),
  80. require_uint256(_positionOf(otherKey) - 1)
  81. );
  82. }
  83. }
  84. invariant absentKeyIsNotStored(bytes32 key, uint256 index)
  85. index < length() => (!contains(key) => at_(index) != key)
  86. {
  87. preserved remove(bytes32 otherKey) {
  88. requireInvariant consistencyIndex(index);
  89. requireInvariant consistencyKey(key);
  90. requireInvariant atUniqueness(index, require_uint256(length() - 1));
  91. }
  92. }
  93. /*
  94. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  95. │ Rule: state only changes by adding or removing elements │
  96. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  97. */
  98. rule stateChange(env e, bytes32 key) {
  99. require lengthSanity();
  100. requireInvariant consistencyKey(key);
  101. requireInvariant absentKeyIsNotStored(key, require_uint256(length() - 1));
  102. uint256 lengthBefore = length();
  103. bool containsBefore = contains(key);
  104. method f;
  105. calldataarg args;
  106. f(e, args);
  107. uint256 lengthAfter = length();
  108. bool containsAfter = contains(key);
  109. assert lengthBefore != lengthAfter => (
  110. (f.selector == sig:add(bytes32).selector && lengthAfter == require_uint256(lengthBefore + 1)) ||
  111. (f.selector == sig:remove(bytes32).selector && lengthAfter == require_uint256(lengthBefore - 1))
  112. );
  113. assert containsBefore != containsAfter => (
  114. (f.selector == sig:add(bytes32).selector && containsAfter) ||
  115. (f.selector == sig:remove(bytes32).selector && containsBefore)
  116. );
  117. }
  118. /*
  119. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  120. │ Rule: check liveness of view functions. │
  121. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  122. */
  123. rule liveness_1(bytes32 key) {
  124. requireInvariant consistencyKey(key);
  125. // contains never revert
  126. contains@withrevert(key);
  127. assert !lastReverted;
  128. }
  129. rule liveness_2(uint256 index) {
  130. requireInvariant consistencyIndex(index);
  131. // length never revert
  132. uint256 length = length@withrevert();
  133. assert !lastReverted;
  134. // at reverts iff the index is out of bound
  135. at_@withrevert(index);
  136. assert !lastReverted <=> index < length;
  137. }
  138. /*
  139. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  140. │ Rule: add key to EnumerableSet if not already contained │
  141. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  142. */
  143. rule add(bytes32 key, bytes32 otherKey) {
  144. require lengthSanity();
  145. uint256 lengthBefore = length();
  146. bool containsBefore = contains(key);
  147. bool containsOtherBefore = contains(otherKey);
  148. bool added = add@withrevert(key);
  149. bool success = !lastReverted;
  150. assert success && contains(key),
  151. "liveness & immediate effect";
  152. assert added <=> !containsBefore,
  153. "return value: added iff not contained";
  154. assert length() == require_uint256(lengthBefore + to_mathint(added ? 1 : 0)),
  155. "effect: length increases iff added";
  156. assert added => at_(lengthBefore) == key,
  157. "effect: add at the end";
  158. assert containsOtherBefore != contains(otherKey) => (added && key == otherKey),
  159. "side effect: other keys are not affected";
  160. }
  161. /*
  162. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  163. │ Rule: remove key from EnumerableSet if already contained │
  164. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  165. */
  166. rule remove(bytes32 key, bytes32 otherKey) {
  167. requireInvariant consistencyKey(key);
  168. requireInvariant consistencyKey(otherKey);
  169. requireInvariant indexedContained(require_uint256(length() - 1));
  170. uint256 lengthBefore = length();
  171. bool containsBefore = contains(key);
  172. bool containsOtherBefore = contains(otherKey);
  173. bool removed = remove@withrevert(key);
  174. bool success = !lastReverted;
  175. assert success && !contains(key),
  176. "liveness & immediate effect";
  177. assert removed <=> containsBefore,
  178. "return value: removed iff contained";
  179. assert length() == require_uint256(lengthBefore - to_mathint(removed ? 1 : 0)),
  180. "effect: length decreases iff removed";
  181. assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey),
  182. "side effect: other keys are not affected";
  183. }
  184. /*
  185. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  186. │ Rule: when adding a new key, the other keys remain in set, at the same index. │
  187. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  188. */
  189. rule addEnumerability(bytes32 key, uint256 index) {
  190. require lengthSanity();
  191. bytes32 atBefore = at_(index);
  192. add(key);
  193. bytes32 atAfter = at_@withrevert(index);
  194. bool atAfterSuccess = !lastReverted;
  195. assert atAfterSuccess;
  196. assert atBefore == atAfter;
  197. }
  198. /*
  199. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  200. │ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │
  201. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  202. */
  203. rule removeEnumerability(bytes32 key, uint256 index) {
  204. uint256 last = require_uint256(length() - 1);
  205. requireInvariant consistencyKey(key);
  206. requireInvariant consistencyIndex(index);
  207. requireInvariant consistencyIndex(last);
  208. requireInvariant indexedContained(index);
  209. bytes32 atBefore = at_(index);
  210. bytes32 lastBefore = at_(last);
  211. bool removed = remove(key);
  212. // can't read last value (length decreased) if an item was removed
  213. bytes32 atAfter = at_@withrevert(index);
  214. assert lastReverted <=> (removed && index == last);
  215. // Cases where a value can change are:
  216. // 1. an item was removed and we are looking at the old last index. In that case the reading reverted.
  217. // 2. an item was removed and we are looking at its old position. In that case the new value is the old lastValue.
  218. // This rule implies that if no item was removed, then atBefore and atAfter must be equal
  219. assert atBefore != atAfter => (
  220. (
  221. removed &&
  222. index == last
  223. ) || (
  224. removed &&
  225. atBefore == key &&
  226. atAfter == lastBefore
  227. )
  228. );
  229. }