ERC20Votes.spec 21 KB

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