EnumerableMap.spec 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import "helpers/helpers.spec"
  2. methods {
  3. // library
  4. set(bytes32,bytes32) returns (bool) envfree
  5. remove(bytes32) returns (bool) envfree
  6. contains(bytes32) returns (bool) envfree
  7. length() returns (uint256) envfree
  8. key_at(uint256) returns (bytes32) envfree
  9. value_at(uint256) returns (bytes32) envfree
  10. tryGet_contains(bytes32) returns (bool) envfree
  11. tryGet_value(bytes32) returns (bytes32) envfree
  12. get(bytes32) returns (bytes32) envfree
  13. // FV
  14. _indexOf(bytes32) returns (uint256) envfree
  15. }
  16. /*
  17. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  18. │ Helpers │
  19. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  20. */
  21. function sanity() returns bool {
  22. return length() < max_uint256;
  23. }
  24. /*
  25. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  26. │ Invariant: the value mapping is empty for keys that are not in the EnumerableMap. │
  27. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  28. */
  29. invariant noValueIfNotContained(bytes32 key)
  30. !contains(key) => tryGet_value(key) == 0
  31. {
  32. preserved set(bytes32 otherKey, bytes32 someValue) {
  33. require sanity();
  34. }
  35. }
  36. /*
  37. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  38. │ Invariant: All indexed keys are contained │
  39. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  40. */
  41. invariant indexedContained(uint256 index)
  42. index < length() => contains(key_at(index))
  43. {
  44. preserved {
  45. requireInvariant consistencyIndex(index);
  46. requireInvariant consistencyIndex(to_uint256(length() - 1));
  47. }
  48. }
  49. /*
  50. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  51. │ Invariant: A value can only be stored at a single location │
  52. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  53. */
  54. invariant atUniqueness(uint256 index1, uint256 index2)
  55. index1 == index2 <=> key_at(index1) == key_at(index2)
  56. {
  57. preserved remove(bytes32 key) {
  58. requireInvariant atUniqueness(index1, to_uint256(length() - 1));
  59. requireInvariant atUniqueness(index2, to_uint256(length() - 1));
  60. }
  61. }
  62. /*
  63. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  64. │ Invariant: index <> value relationship is consistent │
  65. │ │
  66. │ Note that the two consistencyXxx invariants, put together, prove that at_ and _indexOf are inverse of one another. │
  67. │ This proves that we have a bijection between indices (the enumerability part) and keys (the entries that are set │
  68. │ and removed from the EnumerableMap). │
  69. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  70. */
  71. invariant consistencyIndex(uint256 index)
  72. index < length() => _indexOf(key_at(index)) == index + 1
  73. {
  74. preserved remove(bytes32 key) {
  75. requireInvariant consistencyIndex(to_uint256(length() - 1));
  76. }
  77. }
  78. invariant consistencyKey(bytes32 key)
  79. contains(key) => (
  80. _indexOf(key) > 0 &&
  81. _indexOf(key) <= length() &&
  82. key_at(to_uint256(_indexOf(key) - 1)) == key
  83. )
  84. {
  85. preserved remove(bytes32 otherKey) {
  86. requireInvariant consistencyKey(otherKey);
  87. requireInvariant atUniqueness(
  88. to_uint256(_indexOf(key) - 1),
  89. to_uint256(_indexOf(otherKey) - 1)
  90. );
  91. }
  92. }
  93. /*
  94. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  95. │ Rule: state only changes by setting or removing elements │
  96. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  97. */
  98. rule stateChange(env e, bytes32 key) {
  99. require sanity();
  100. requireInvariant consistencyKey(key);
  101. uint256 lengthBefore = length();
  102. bool containsBefore = contains(key);
  103. bytes32 valueBefore = tryGet_value(key);
  104. method f;
  105. calldataarg args;
  106. f(e, args);
  107. uint256 lengthAfter = length();
  108. bool containsAfter = contains(key);
  109. bytes32 valueAfter = tryGet_value(key);
  110. assert lengthBefore != lengthAfter => (
  111. (f.selector == set(bytes32,bytes32).selector && lengthAfter == lengthBefore + 1) ||
  112. (f.selector == remove(bytes32).selector && lengthAfter == lengthBefore - 1)
  113. );
  114. assert containsBefore != containsAfter => (
  115. (f.selector == set(bytes32,bytes32).selector && containsAfter) ||
  116. (f.selector == remove(bytes32).selector && !containsAfter)
  117. );
  118. assert valueBefore != valueAfter => (
  119. (f.selector == set(bytes32,bytes32).selector && containsAfter) ||
  120. (f.selector == remove(bytes32).selector && !containsAfter && valueAfter == 0)
  121. );
  122. }
  123. /*
  124. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  125. │ Rule: check liveness of view functions. │
  126. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  127. */
  128. rule liveness_1(bytes32 key) {
  129. requireInvariant consistencyKey(key);
  130. // contains never revert
  131. bool contains = contains@withrevert(key);
  132. assert !lastReverted;
  133. // tryGet never reverts (key)
  134. tryGet_contains@withrevert(key);
  135. assert !lastReverted;
  136. // tryGet never reverts (value)
  137. tryGet_value@withrevert(key);
  138. assert !lastReverted;
  139. // get reverts iff the key is not in the map
  140. get@withrevert(key);
  141. assert !lastReverted <=> contains;
  142. }
  143. rule liveness_2(uint256 index) {
  144. requireInvariant consistencyIndex(index);
  145. // length never revert
  146. uint256 length = length@withrevert();
  147. assert !lastReverted;
  148. // key_at reverts iff the index is out of bound
  149. key_at@withrevert(index);
  150. assert !lastReverted <=> index < length;
  151. // value_at reverts iff the index is out of bound
  152. value_at@withrevert(index);
  153. assert !lastReverted <=> index < length;
  154. }
  155. /*
  156. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  157. │ Rule: get and tryGet return the expected values. │
  158. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  159. */
  160. rule getAndTryGet(bytes32 key) {
  161. requireInvariant noValueIfNotContained(key);
  162. bool contained = contains(key);
  163. bool tryContained = tryGet_contains(key);
  164. bytes32 tryValue = tryGet_value(key);
  165. bytes32 value = get@withrevert(key); // revert is not contained
  166. assert contained == tryContained;
  167. assert contained => tryValue == value;
  168. assert !contained => tryValue == 0;
  169. }
  170. /*
  171. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  172. │ Rule: set key-value in EnumerableMap │
  173. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  174. */
  175. rule set(bytes32 key, bytes32 value, bytes32 otherKey) {
  176. require sanity();
  177. uint256 lengthBefore = length();
  178. bool containsBefore = contains(key);
  179. bool containsOtherBefore = contains(otherKey);
  180. bytes32 otherValueBefore = tryGet_value(otherKey);
  181. bool added = set@withrevert(key, value);
  182. bool success = !lastReverted;
  183. assert success && contains(key) && get(key) == value,
  184. "liveness & immediate effect";
  185. assert added <=> !containsBefore,
  186. "return value: added iff not contained";
  187. assert length() == lengthBefore + to_mathint(added ? 1 : 0),
  188. "effect: length increases iff added";
  189. assert added => (key_at(lengthBefore) == key && value_at(lengthBefore) == value),
  190. "effect: add at the end";
  191. assert containsOtherBefore != contains(otherKey) => (added && key == otherKey),
  192. "side effect: other keys are not affected";
  193. assert otherValueBefore != tryGet_value(otherKey) => key == otherKey,
  194. "side effect: values attached to other keys are not affected";
  195. }
  196. /*
  197. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  198. │ Rule: remove key from EnumerableMap │
  199. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  200. */
  201. rule remove(bytes32 key, bytes32 otherKey) {
  202. requireInvariant consistencyKey(key);
  203. requireInvariant consistencyKey(otherKey);
  204. uint256 lengthBefore = length();
  205. bool containsBefore = contains(key);
  206. bool containsOtherBefore = contains(otherKey);
  207. bytes32 otherValueBefore = tryGet_value(otherKey);
  208. bool removed = remove@withrevert(key);
  209. bool success = !lastReverted;
  210. assert success && !contains(key),
  211. "liveness & immediate effect";
  212. assert removed <=> containsBefore,
  213. "return value: removed iff contained";
  214. assert length() == lengthBefore - to_mathint(removed ? 1 : 0),
  215. "effect: length decreases iff removed";
  216. assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey),
  217. "side effect: other keys are not affected";
  218. assert otherValueBefore != tryGet_value(otherKey) => key == otherKey,
  219. "side effect: values attached to other keys are not affected";
  220. }
  221. /*
  222. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  223. │ Rule: when adding a new key, the other keys remain in set, at the same index. │
  224. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  225. */
  226. rule setEnumerability(bytes32 key, bytes32 value, uint256 index) {
  227. require sanity();
  228. bytes32 atKeyBefore = key_at(index);
  229. bytes32 atValueBefore = value_at(index);
  230. set(key, value);
  231. bytes32 atKeyAfter = key_at@withrevert(index);
  232. assert !lastReverted;
  233. bytes32 atValueAfter = value_at@withrevert(index);
  234. assert !lastReverted;
  235. assert atKeyAfter == atKeyBefore;
  236. assert atValueAfter != atValueBefore => (
  237. key == atKeyBefore &&
  238. value == atValueAfter
  239. );
  240. }
  241. /*
  242. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  243. │ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │
  244. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  245. */
  246. rule removeEnumerability(bytes32 key, uint256 index) {
  247. uint256 last = length() - 1;
  248. requireInvariant consistencyKey(key);
  249. requireInvariant consistencyIndex(index);
  250. requireInvariant consistencyIndex(last);
  251. bytes32 atKeyBefore = key_at(index);
  252. bytes32 atValueBefore = value_at(index);
  253. bytes32 lastKeyBefore = key_at(last);
  254. bytes32 lastValueBefore = value_at(last);
  255. remove(key);
  256. // can't read last value & keys (length decreased)
  257. bytes32 atKeyAfter = key_at@withrevert(index);
  258. assert lastReverted <=> index == last;
  259. bytes32 atValueAfter = value_at@withrevert(index);
  260. assert lastReverted <=> index == last;
  261. // One value that is allowed to change is if previous value was removed,
  262. // in that case the last value before took its place.
  263. assert (
  264. index != last &&
  265. atKeyBefore != atKeyAfter
  266. ) => (
  267. atKeyBefore == key &&
  268. atKeyAfter == lastKeyBefore
  269. );
  270. assert (
  271. index != last &&
  272. atValueBefore != atValueAfter
  273. ) => (
  274. atValueAfter == lastValueBefore
  275. );
  276. }