EnumerableMap.spec 20 KB

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