EnumerableSet.spec 14 KB

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