EnumerableMap.spec 19 KB

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