ERC20Votes.spec 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. using ERC20VotesHarness as erc20votes
  2. methods {
  3. // functions
  4. checkpoints(address, uint32) envfree
  5. numCheckpoints(address) returns (uint32) envfree
  6. delegates(address) returns (address) envfree
  7. getVotes(address) returns (uint256) envfree
  8. getPastVotes(address, uint256) returns (uint256)
  9. getPastTotalSupply(uint256) returns (uint256)
  10. delegate(address)
  11. _delegate(address, address)
  12. // delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32)
  13. totalSupply() returns (uint256) envfree
  14. _maxSupply() returns (uint224) envfree
  15. // harnesss functions
  16. ckptFromBlock(address, uint32) returns (uint32) envfree
  17. ckptVotes(address, uint32) returns (uint224) envfree
  18. mint(address, uint256)
  19. burn(address, uint256)
  20. // solidity generated getters
  21. _delegates(address) returns (address) envfree
  22. // external functions
  23. }
  24. // gets the most recent votes for a user
  25. ghost userVotes(address) returns uint224;
  26. // sums the total votes for all users
  27. ghost totalVotes() returns mathint {
  28. axiom totalVotes() >= 0;
  29. }
  30. // helper
  31. invariant totalVotes_gte_accounts(address a, address b)
  32. totalVotes() >= getVotes(a) + getVotes(b)
  33. hook Sstore _checkpoints[KEY address account][INDEX uint32 index].votes uint224 newVotes (uint224 oldVotes) STORAGE {
  34. havoc userVotes assuming
  35. userVotes@new(account) == newVotes;
  36. havoc totalVotes assuming
  37. totalVotes@new() == totalVotes@old() + to_mathint(newVotes) - to_mathint(userVotes(account));
  38. }
  39. ghost lastFromBlock(address) returns uint32;
  40. ghost doubleFromBlock(address) returns bool {
  41. init_state axiom forall address a. doubleFromBlock(a) == false;
  42. }
  43. hook Sstore _checkpoints[KEY address account][INDEX uint32 index].fromBlock uint32 newBlock (uint32 oldBlock) STORAGE {
  44. havoc lastFromBlock assuming
  45. lastFromBlock@new(account) == newBlock;
  46. havoc doubleFromBlock assuming
  47. doubleFromBlock@new(account) == (newBlock == oldBlock);
  48. }
  49. rule sanity(method f) {
  50. env e;
  51. calldataarg arg;
  52. f(e, arg);
  53. assert false;
  54. }
  55. // something stupid just to see
  56. invariant sanity_invariant()
  57. totalSupply() >= 0
  58. // sum of user balances is >= total amount of delegated votes
  59. invariant votes_solvency()
  60. to_mathint(totalSupply()) >= totalVotes()
  61. // for some checkpoint, the fromBlock is less than the current block number
  62. // passes but fails rule sanity from hash on delegate by sig
  63. invariant timestamp_constrains_fromBlock(address account, uint32 index, env e)
  64. ckptFromBlock(account, index) < e.block.number
  65. {
  66. preserved {
  67. require index < numCheckpoints(account);
  68. }
  69. }
  70. // numCheckpoints are less than maxInt
  71. // passes
  72. invariant maxInt_constrains_numBlocks(address account)
  73. numCheckpoints(account) <= 4294967295 // 2^32
  74. // can't have more checkpoints for a given account than the last from block
  75. invariant fromBlock_constrains_numBlocks(address account)
  76. numCheckpoints(account) <= lastFromBlock(account)
  77. // this fails, which makes sense because there is no require about the previous fromBlock
  78. invariant unique_checkpoints(address account)
  79. !doubleFromBlock(account)
  80. // if an account has been delegated too, then both accounts must have a checkpoint
  81. invariant delegated_implies_checkpoints(address delegator, address delegatee)
  82. delegates(delegator) == delegatee => numCheckpoints(delegator) > 0 && numCheckpoints(delegatee) > 0
  83. { preserved with (env e) {
  84. require delegatee != 0;
  85. require balanceOf(e, delegator) > 0;
  86. }}
  87. // assumes neither account has delegated
  88. rule transfer_safe() {
  89. env e;
  90. uint256 amount;
  91. address a; address b;
  92. require a != b;
  93. // requireInvariant totalVotes_gte_accounts(a, b);
  94. address delegateA = delegates(a);
  95. address delegateB = delegates(b);
  96. uint256 votesA_pre = getVotes(delegateA);
  97. uint256 votesB_pre = getVotes(delegateB);
  98. mathint totalVotes_pre = totalVotes();
  99. erc20votes.transferFrom(e, a, b, amount);
  100. mathint totalVotes_post = totalVotes();
  101. uint256 votesA_post = getVotes(delegateA);
  102. uint256 votesB_post = getVotes(delegateB);
  103. // if an account that has not delegated transfers balance to an account that has, it will increase the total supply of votes
  104. assert totalVotes_pre == totalVotes_post, "transfer changed total supply";
  105. assert delegateA == delegates(a) && delegateB == delegates(b), "delegates changed by transfer";
  106. assert delegateA != 0 => votesA_pre - votesA_post == amount, "a lost the proper amount of votes";
  107. assert delegateB != 0 => votesB_post - votesB_pre == amount, "b lost the proper amount of votes";
  108. }
  109. rule delegator_votes_removed() {
  110. env e;
  111. address delegator; address delegatee;
  112. require delegator != delegatee;
  113. require delegates(delegator) == 0; // has not delegated
  114. uint256 pre = getVotes(delegator);
  115. _delegate(e, delegator, delegatee);
  116. uint256 balance = balanceOf(e, delegator);
  117. uint256 post = getVotes(delegator);
  118. assert post == pre - balance, "delegator retained votes";
  119. }
  120. rule delegatee_receives_votes() {
  121. env e;
  122. address delegator; address delegatee;
  123. require delegator != delegatee;
  124. require delegates(delegator) != delegatee;
  125. uint256 delegator_bal = balanceOf(e, delegator);
  126. uint256 votes_= getVotes(delegatee);
  127. _delegate(e, delegator, delegatee);
  128. uint256 _votes = getVotes(delegatee);
  129. assert _votes == votes_ + delegator_bal, "delegatee did not receive votes";
  130. }
  131. rule previous_delegatee_zeroed() {
  132. env e;
  133. address delegator; address delegatee; address third;
  134. require third != delegatee;
  135. require third != delegator;
  136. require delegates(delegator) == third;
  137. // for third to have been delegated to, it must have a checkpoint
  138. uint256 delegator_bal = balanceOf(e, delegator);
  139. uint256 votes_ = getVotes(third);
  140. _delegate(e, delegator, delegatee);
  141. uint256 _votes = getVotes(third);
  142. assert _votes == votes_ - delegator_bal, "votes not removed from the previous delegatee";
  143. }
  144. // passes with rule sanity
  145. rule delegate_contained() {
  146. env e;
  147. address delegator; address delegatee; address other;
  148. require other != delegatee;
  149. require other != delegates(delegator);
  150. uint256 votes_ = getVotes(other);
  151. _delegate(e, delegator, delegatee);
  152. uint256 _votes = getVotes(other);
  153. assert votes_ == _votes, "votes not contained";
  154. }
  155. // checks all of the above rules with front running
  156. rule delegate_no_frontrunning(method f) {
  157. env e; calldataarg args;
  158. address delegator; address delegatee; address third; address other;
  159. require delegator != delegatee;
  160. require delegates(delegator) == third;
  161. require other != third;
  162. uint256 delegator_bal = erc20votes.balanceOf(e, delegator);
  163. uint256 dr_ = getVotes(delegator);
  164. uint256 de_ = getVotes(delegatee);
  165. uint256 third_ = getVotes(third);
  166. uint256 other_ = getVotes(other);
  167. // require third is address for previous delegator
  168. f(e, args);
  169. _delegate(e, delegator, delegatee);
  170. uint256 _dr = getVotes(delegator);
  171. uint256 _de = getVotes(delegatee);
  172. uint256 _third = getVotes(third);
  173. uint256 _other = getVotes(other);
  174. // delegator loses all of their votes
  175. // delegatee gains that many votes
  176. // third loses any votes delegated to them
  177. assert _dr == 0, "delegator retained votes";
  178. assert _de == de_ + delegator_bal, "delegatee not received votes";
  179. assert _third != 0 => _third == third_ - delegator_bal, "votes not removed from third";
  180. assert other_ == _other, "delegate not contained";
  181. }
  182. // passes
  183. rule mint_increases_totalSupply() {
  184. env e;
  185. uint256 amount; address account;
  186. uint256 fromBlock = e.block.number;
  187. uint256 totalSupply_ = totalSupply();
  188. mint(e, account, amount);
  189. uint256 _totalSupply = totalSupply();
  190. require _totalSupply < _maxSupply();
  191. assert _totalSupply == totalSupply_ + amount, "totalSupply not increased properly";
  192. assert getPastTotalSupply(e, fromBlock) == totalSupply_ , "previous total supply not saved properly";
  193. }
  194. // passes
  195. rule burn_decreases_totalSupply() {
  196. env e;
  197. uint256 amount; address account;
  198. uint256 fromBlock = e.block.number;
  199. uint256 totalSupply_ = totalSupply();
  200. require totalSupply_ > balanceOf(e, account);
  201. burn(e, account, amount);
  202. uint256 _totalSupply = totalSupply();
  203. require _totalSupply < _maxSupply();
  204. assert _totalSupply == totalSupply_ - amount, "totalSupply not decreased properly";
  205. assert getPastTotalSupply(e, fromBlock) == totalSupply_ , "previous total supply not saved properly";
  206. }
  207. // passes
  208. rule mint_doesnt_increase_totalVotes() {
  209. env e;
  210. uint256 amount; address account;
  211. mathint totalVotes_ = totalVotes();
  212. mint(e, account, amount);
  213. assert totalVotes() == totalVotes_, "totalVotes increased";
  214. }
  215. // passes
  216. rule burn_doesnt_decrease_totalVotes() {
  217. env e;
  218. uint256 amount; address account;
  219. mathint totalVotes_ = totalVotes();
  220. burn(e, account, amount);
  221. assert totalVotes() == totalVotes_, "totalVotes decreased";
  222. }
  223. // // fails
  224. // rule mint_increases_totalVotes() {
  225. // env e;
  226. // uint256 amount; address account;
  227. // mathint totalVotes_ = totalVotes();
  228. // mint(e, account, amount);
  229. // assert totalVotes() == totalVotes_ + to_mathint(amount), "totalVotes not increased";
  230. // }
  231. // // fails
  232. // rule burn_decreases_totalVotes() {
  233. // env e;
  234. // uint256 amount; address account;
  235. // mathint totalVotes_ = totalVotes();
  236. // burn(e, account, amount);
  237. // assert totalVotes() == totalVotes_ - to_mathint(amount), "totalVotes not decreased";
  238. // }