ERC20Votes.spec 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. import "helpers.spec"
  2. import "methods/IERC20.spec"
  3. import "methods/IERC5805.spec"
  4. import "methods/IERC6372.spec"
  5. methods {
  6. numCheckpoints(address) returns (uint32) envfree
  7. ckptFromBlock(address, uint32) returns (uint32) envfree
  8. ckptVotes(address, uint32) returns (uint224) envfree
  9. numCheckpointsTotalSupply() returns (uint32) envfree
  10. ckptFromBlockTotalSupply(uint32) returns (uint32) envfree
  11. ckptVotesTotalSupply(uint32) returns (uint224) envfree
  12. maxSupply() returns (uint224) envfree
  13. }
  14. /*
  15. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  16. │ Helpers │
  17. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  18. */
  19. definition max_uint224() returns uint224 = 0xffffffffffffffffffffffffffff;
  20. /*
  21. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  22. │ Ghost & hooks: total delegated │
  23. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  24. */
  25. // copied from ERC20.spec (can't be imported because of hook conflicts)
  26. ghost sumOfBalances() returns uint256 {
  27. init_state axiom sumOfBalances() == 0;
  28. }
  29. ghost mapping(address => uint256) balanceOf {
  30. init_state axiom forall address a. balanceOf[a] == 0;
  31. }
  32. ghost mapping(address => address) delegates {
  33. init_state axiom forall address a. delegates[a] == 0;
  34. }
  35. ghost mapping(address => uint256) getVotes {
  36. init_state axiom forall address a. getVotes[a] == 0;
  37. }
  38. hook Sstore _balances[KEY address account] uint256 newAmount (uint256 oldAmount) STORAGE {
  39. // copied from ERC20.spec (can't be imported because of hook conflicts)
  40. havoc sumOfBalances assuming sumOfBalances@new() == sumOfBalances@old() + newAmount - oldAmount;
  41. balanceOf[account] = newAmount;
  42. getVotes[delegates[account]] = getVotes[delegates[account]] + newAmount - oldAmount;
  43. }
  44. hook Sstore _delegates[KEY address account] address newDelegate (address oldDelegate) STORAGE {
  45. delegates[account] = newDelegate;
  46. getVotes[oldDelegate] = getVotes[oldDelegate] - balanceOf[account];
  47. getVotes[newDelegate] = getVotes[newDelegate] + balanceOf[account];
  48. }
  49. // all votes (total supply) minus the votes balances delegated to 0
  50. definition totalVotes() returns uint256 = sumOfBalances() - getVotes[0];
  51. /*
  52. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  53. │ Invariant: copied from ERC20.spec (can't be imported because of hook conflicts) │
  54. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  55. */
  56. invariant totalSupplyIsSumOfBalances()
  57. totalSupply() == sumOfBalances() &&
  58. totalSupply() <= max_uint256
  59. /*
  60. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  61. │ Invariant: clock │
  62. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  63. */
  64. invariant clockMode(env e)
  65. clock(e) == e.block.number || clock(e) == e.block.timestamp
  66. /*
  67. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  68. │ Invariant: zero address has no delegate, no votes and no checkpoints │
  69. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  70. */
  71. invariant zeroConsistency()
  72. delegates(0) == 0 &&
  73. getVotes(0) == 0 &&
  74. numCheckpoints(0) == 0
  75. {
  76. preserved with (env e) {
  77. // we assume address 0 cannot perform any call
  78. require e.msg.sender != 0;
  79. }
  80. }
  81. // WIP
  82. invariant delegateHasCheckpoint(address a)
  83. (balanceOf(a) > 0 && delegates(a) != 0) => numCheckpoints(delegates(a)) > 0
  84. {
  85. preserved delegate(address delegatee) with (env e) {
  86. require numCheckpoints(delegatee) < max_uint256;
  87. }
  88. preserved delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) with (env e) {
  89. require numCheckpoints(delegatee) < max_uint256;
  90. }
  91. }
  92. /*
  93. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  94. │ Invariant: hook correctly track latest checkpoint │
  95. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  96. */
  97. invariant balanceAndDelegationConsistency(address a)
  98. balanceOf(a) == balanceOf[a] &&
  99. delegates(a) == delegates[a]
  100. // WIP
  101. invariant votesConsistency(address a)
  102. a != 0 => getVotes(a) == getVotes[a]
  103. // WIP
  104. invariant voteBiggerThanDelegatedBalances(address a)
  105. getVotes(delegates(a)) >= balanceOf(a)
  106. {
  107. preserved {
  108. requireInvariant zeroConsistency();
  109. }
  110. }
  111. // WIP
  112. invariant userVotesLessTotalVotes(address a)
  113. numCheckpoints(a) > 0 => getVotes(a) <= totalVotes()
  114. {
  115. preserved {
  116. requireInvariant totalSupplyIsSumOfBalances;
  117. }
  118. }
  119. /*
  120. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  121. │ Invariant: totalSupply is checkpointed │
  122. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  123. */
  124. invariant totalSupplyTracked()
  125. totalSupply() > 0 => numCheckpointsTotalSupply() > 0
  126. invariant totalSupplyLatest()
  127. numCheckpointsTotalSupply() > 0 => ckptVotesTotalSupply(numCheckpointsTotalSupply() - 1) == totalSupply()
  128. /*
  129. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  130. │ Invariant: checkpoint is not in the future │
  131. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  132. */
  133. invariant checkpointInThePast(env e, address a)
  134. numCheckpoints(a) > 0 => ckptFromBlock(a, numCheckpoints(a) - 1) <= clock(e)
  135. {
  136. preserved with (env e2) {
  137. require clock(e2) <= clock(e);
  138. }
  139. }
  140. invariant totalCheckpointInThePast(env e)
  141. numCheckpointsTotalSupply() > 0 => ckptFromBlockTotalSupply(numCheckpointsTotalSupply() - 1) <= clock(e)
  142. {
  143. preserved with (env e2) {
  144. require clock(e2) <= clock(e);
  145. }
  146. }
  147. /*
  148. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  149. │ Invariant: checkpoint clock is strictly increassing (implies no duplicate) │
  150. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  151. */
  152. invariant checkpointClockIncreassing(address a)
  153. numCheckpoints(a) > 1 => ckptFromBlock(a, numCheckpoints(a) - 2) < ckptFromBlock(a, numCheckpoints(a) - 1)
  154. {
  155. preserved with (env e) {
  156. requireInvariant checkpointInThePast(e, a);
  157. }
  158. }
  159. invariant totalCheckpointClockIncreassing()
  160. numCheckpointsTotalSupply() > 1 => ckptFromBlockTotalSupply(numCheckpointsTotalSupply() - 2) < ckptFromBlockTotalSupply(numCheckpointsTotalSupply() - 1)
  161. {
  162. preserved with (env e) {
  163. requireInvariant totalCheckpointInThePast(e);
  164. }
  165. }
  166. /*
  167. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  168. │ Invariant: Don't track votes delegated to address 0 │
  169. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  170. */
  171. rule checkpointsImmutable(env e, method f)
  172. filtered { f -> !f.isView }
  173. {
  174. address account;
  175. uint32 index;
  176. require index < numCheckpoints(account);
  177. uint224 valueBefore = ckptVotes(account, index);
  178. uint32 clockBefore = ckptFromBlock(account, index);
  179. calldataarg args; f(e, args);
  180. uint224 valueAfter = ckptVotes@withrevert(account, index);
  181. assert !lastReverted;
  182. uint32 clockAfter = ckptFromBlock@withrevert(account, index);
  183. assert !lastReverted;
  184. assert clockAfter == clockBefore;
  185. assert valueAfter != valueBefore => clockBefore == clock(e);
  186. }
  187. rule totalCheckpointsImmutable(env e, method f)
  188. filtered { f -> !f.isView }
  189. {
  190. uint32 index;
  191. require index < numCheckpointsTotalSupply();
  192. uint224 valueBefore = ckptVotesTotalSupply(index);
  193. uint32 clockBefore = ckptFromBlockTotalSupply(index);
  194. calldataarg args; f(e, args);
  195. uint224 valueAfter = ckptVotesTotalSupply@withrevert(index);
  196. assert !lastReverted;
  197. uint32 clockAfter = ckptFromBlockTotalSupply@withrevert(index);
  198. assert !lastReverted;
  199. assert clockAfter == clockBefore;
  200. assert valueAfter != valueBefore => clockBefore == clock(e);
  201. }
  202. /*
  203. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  204. │ Rules: what function can lead to state changes │
  205. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  206. */
  207. rule changes(env e, method f)
  208. filtered { f -> !f.isView }
  209. {
  210. address account;
  211. calldataarg args;
  212. uint32 ckptsBefore = numCheckpoints(account);
  213. uint256 votesBefore = getVotes(account);
  214. address delegatesBefore = delegates(account);
  215. f(e, args);
  216. uint32 ckptsAfter = numCheckpoints(account);
  217. uint256 votesAfter = getVotes(account);
  218. address delegatesAfter = delegates(account);
  219. assert ckptsAfter != ckptsBefore => (
  220. ckptsAfter == ckptsBefore + 1 &&
  221. ckptFromBlock(account, ckptsAfter - 1) == clock(e) &&
  222. (
  223. f.selector == mint(address,uint256).selector ||
  224. f.selector == burn(address,uint256).selector ||
  225. f.selector == transfer(address,uint256).selector ||
  226. f.selector == transferFrom(address,address,uint256).selector ||
  227. f.selector == delegate(address).selector ||
  228. f.selector == delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32).selector
  229. )
  230. );
  231. assert votesAfter != votesBefore => (
  232. f.selector == mint(address,uint256).selector ||
  233. f.selector == burn(address,uint256).selector ||
  234. f.selector == transfer(address,uint256).selector ||
  235. f.selector == transferFrom(address,address,uint256).selector ||
  236. f.selector == delegate(address).selector ||
  237. f.selector == delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32).selector
  238. );
  239. assert delegatesAfter != delegatesBefore => (
  240. f.selector == delegate(address).selector ||
  241. f.selector == delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32).selector
  242. );
  243. }
  244. /*
  245. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  246. │ Rules: mint updates votes │
  247. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  248. */
  249. /* WIP
  250. rule mint(env e) {
  251. requireInvariant totalSupplyIsSumOfBalances();
  252. requireInvariant totalSupplyTracked();
  253. requireInvariant totalSupplyLatest();
  254. require nonpayable(e);
  255. address to;
  256. address toDelegate = delegates(to);
  257. address other;
  258. uint256 amount;
  259. // cache state
  260. uint256 totalSupplyBefore = totalSupply();
  261. uint256 votesBefore = getVotes(toDelegate);
  262. uint32 ckptsBefore = numCheckpoints(toDelegate);
  263. mathint clockBefore = ckptsBefore == 0 ? -1 : numCheckpoints(toDelegate);
  264. uint256 otherVotesBefore = getVotes(other);
  265. uint256 otherCkptsBefore = numCheckpoints(other);
  266. // run transaction
  267. mint@withrevert(e, to, amount);
  268. bool success = !lastReverted;
  269. uint256 votesAfter = getVotes(toDelegate);
  270. uint32 ckptsAfter = numCheckpoints(toDelegate);
  271. mathint clockAfter = ckptsAfter == 0 ? -1 : numCheckpoints(toDelegate);
  272. uint256 otherVotesAfter = getVotes(other);
  273. uint256 otherCkptsAfter = numCheckpoints(other);
  274. // liveness
  275. assert success <=> (to != 0 || totalSupplyBefore + amount <= max_uint256);
  276. // effects
  277. assert (
  278. success &&
  279. toDelegate != 0
  280. ) => (
  281. votesAfter == votesBefore + amount &&
  282. ckptsAfter == ckptsBefore + to_mathint(clockBefore == clockAfter ? 0 : 1) &&
  283. clockAfter == clock(e)
  284. );
  285. // no side effects
  286. assert otherVotesAfter != otherVotesBefore => other == toDelegate;
  287. assert otherCkptsAfter != otherCkptsBefore => other == toDelegate;
  288. }
  289. */
  290. /*
  291. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  292. │ Rules: burn updates votes │
  293. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  294. */
  295. /* WIP
  296. rule burn(env e) {
  297. requireInvariant totalSupplyIsSumOfBalances();
  298. requireInvariant totalSupplyTracked();
  299. requireInvariant totalSupplyLatest();
  300. require nonpayable(e);
  301. address from;
  302. address fromDelegate = delegates(from);
  303. address other;
  304. uint256 amount;
  305. // cache state
  306. uint256 fromBalanceBefore = balanceOf(from);
  307. uint256 votesBefore = getVotes(fromDelegate);
  308. uint32 ckptsBefore = numCheckpoints(fromDelegate);
  309. mathint clockBefore = ckptsBefore == 0 ? -1 : numCheckpoints(fromDelegate);
  310. uint256 otherVotesBefore = getVotes(other);
  311. uint256 otherCkptsBefore = numCheckpoints(other);
  312. // run transaction
  313. burn@withrevert(e, from, amount);
  314. bool success = !lastReverted;
  315. uint256 votesAfter = getVotes(fromDelegate);
  316. uint32 ckptsAfter = numCheckpoints(fromDelegate);
  317. mathint clockAfter = ckptsAfter == 0 ? -1 : numCheckpoints(fromDelegate);
  318. uint256 otherVotesAfter = getVotes(other);
  319. uint256 otherCkptsAfter = numCheckpoints(other);
  320. // liveness
  321. assert success <=> (from != 0 || amount <= fromBalanceBefore);
  322. // effects
  323. assert (
  324. success &&
  325. fromDelegate != 0
  326. ) => (
  327. votesAfter == votesBefore + amount &&
  328. ckptsAfter == ckptsBefore + to_mathint(clockBefore == clockAfter ? 0 : 1) &&
  329. clockAfter == clock(e)
  330. );
  331. // no side effects
  332. assert otherVotesAfter != otherVotesBefore => other == fromDelegate;
  333. assert otherCkptsAfter != otherCkptsBefore => other == fromDelegate;
  334. }
  335. */