ERC721Votes.spec 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. using Checkpoints as Checkpoints
  2. methods {
  3. // functions
  4. checkpoints(address, uint32) envfree
  5. numCheckpoints(address) returns (uint32) envfree
  6. getVotes(address) returns (uint256) envfree
  7. getPastVotes(address, uint256) returns (uint256)
  8. getPastTotalSupply(uint256) returns (uint256)
  9. delegates(address) returns (address) envfree
  10. delegate(address)
  11. _delegate(address, address)
  12. delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32)
  13. nonces(address) returns (uint256)
  14. totalSupply() returns (uint256) envfree
  15. _maxSupply() returns (uint224) envfree
  16. // harnesss functions
  17. ckptFromBlock(address, uint32) returns (uint32) envfree
  18. ckptVotes(address, uint32) returns (uint224) envfree
  19. mint(address, uint256)
  20. burn(uint256)
  21. unsafeNumCheckpoints(address) returns (uint256) envfree
  22. // solidity generated getters
  23. _delegation(address) returns (address) envfree
  24. // external functions
  25. }
  26. // gets the most recent votes for a user
  27. ghost userVotes(address) returns uint224{
  28. init_state axiom forall address a. userVotes(a) == 0;
  29. }
  30. // sums the total votes for all users
  31. ghost totalVotes() returns mathint {
  32. init_state axiom totalVotes() == 0;
  33. axiom totalVotes() >= 0;
  34. }
  35. hook Sstore _checkpoints[KEY address account].votes uint224 newVotes (uint224 oldVotes) STORAGE {
  36. havoc userVotes assuming
  37. userVotes@new(account) == newVotes;
  38. havoc totalVotes assuming
  39. totalVotes@new() == totalVotes@old() + to_mathint(newVotes) - to_mathint(userVotes(account));
  40. }
  41. ghost lastFromBlock(address) returns uint32;
  42. ghost doubleFromBlock(address) returns bool {
  43. init_state axiom forall address a. doubleFromBlock(a) == false;
  44. }
  45. hook Sstore _checkpoints[KEY address account].fromBlock uint32 newBlock (uint32 oldBlock) STORAGE {
  46. havoc lastFromBlock assuming
  47. lastFromBlock@new(account) == newBlock;
  48. havoc doubleFromBlock assuming
  49. doubleFromBlock@new(account) == (newBlock == lastFromBlock(account));
  50. }
  51. rule sanity(method f) {
  52. env e;
  53. calldataarg arg;
  54. f(e, arg);
  55. assert false;
  56. }
  57. // something stupid just to see
  58. invariant sanity_invariant()
  59. totalSupply() >= 0
  60. // sum of user balances is >= total amount of delegated votes
  61. // blocked by tool error
  62. invariant votes_solvency()
  63. to_mathint(totalSupply()) >= totalVotes()
  64. { preserved with(env e) {
  65. require forall address account. numCheckpoints(account) < 1000000;
  66. // requireInvariant totalVotes_sums_accounts();
  67. } }
  68. // invariant totalVotes_sums_accounts()
  69. // forall address a. forall address b. (a != b && a != 0x0 && b != 0x0) => totalVotes() >= getVotes(delegates(a)) + getVotes(delegates(b))
  70. // for some checkpoint, the fromBlock is less than the current block number
  71. // passes but fails rule sanity from hash on delegate by sig
  72. invariant timestamp_constrains_fromBlock(address account, uint32 index, env e)
  73. ckptFromBlock(account, index) < e.block.number
  74. {
  75. preserved {
  76. require index < numCheckpoints(account);
  77. }
  78. }
  79. // TODO add a note about this in the report
  80. // // numCheckpoints are less than maxInt
  81. // // passes because numCheckpoints does a safeCast
  82. // invariant maxInt_constrains_numBlocks(address account)
  83. // numCheckpoints(account) < 4294967295 // 2^32
  84. // // fails because there are no checks to stop it
  85. // invariant maxInt_constrains_ckptsLength(address account)
  86. // unsafeNumCheckpoints(account) < 4294967295 // 2^32
  87. // can't have more checkpoints for a given account than the last from block
  88. // passes
  89. invariant fromBlock_constrains_numBlocks(address account)
  90. numCheckpoints(account) <= ckptFromBlock(account, numCheckpoints(account) - 1)
  91. { preserved with(env e) {
  92. require e.block.number >= ckptFromBlock(account, numCheckpoints(account) - 1); // this should be true from the invariant above!!
  93. }}
  94. // for any given checkpoint, the fromBlock must be greater than the checkpoint
  95. // this proves the above invariant in combination with the below invariant
  96. // if checkpoint has a greater fromBlock than the last, and the FromBlock is always greater than the pos.
  97. // Then the number of positions must be less than the currentFromBlock
  98. // ^note that the tool is assuming it's possible for the starting fromBlock to be 0 or anything, and does not know the current starting block
  99. // passes + rule sanity
  100. invariant fromBlock_greaterThanEq_pos(address account, uint32 pos)
  101. ckptFromBlock(account, pos) >= pos
  102. // a larger index must have a larger fromBlock
  103. // passes + rule sanity
  104. invariant fromBlock_increasing(address account, uint32 pos, uint32 pos2)
  105. pos > pos2 => ckptFromBlock(account, pos) > ckptFromBlock(account, pos2)
  106. // converted from an invariant to a rule to slightly change the logic
  107. // if the fromBlock is the same as before, then the number of checkpoints stays the same
  108. // however if the fromBlock is new than the number of checkpoints increases
  109. // passes, fails rule sanity because tautology check seems to be bugged
  110. rule unique_checkpoints_rule(method f) {
  111. env e; calldataarg args;
  112. address account;
  113. uint32 num_ckpts_ = numCheckpoints(account);
  114. uint32 fromBlock_ = num_ckpts_ == 0 ? 0 : ckptFromBlock(account, num_ckpts_ - 1);
  115. f(e, args);
  116. uint32 _num_ckpts = numCheckpoints(account);
  117. uint32 _fromBlock = _num_ckpts == 0 ? 0 : ckptFromBlock(account, _num_ckpts - 1);
  118. assert fromBlock_ == _fromBlock => num_ckpts_ == _num_ckpts || _num_ckpts == 1, "same fromBlock, new checkpoint";
  119. // this assert fails consistently
  120. // assert !doubleFromBlock(account) => ckpts_ != _ckpts, "new fromBlock but total checkpoints not being increased";
  121. }
  122. // assumes neither account has delegated
  123. // currently fails due to this scenario. A has maxint number of checkpoints
  124. // an additional checkpoint is added which overflows and sets A's votes to 0
  125. // passes + rule sanity (- a bad tautology check)
  126. rule transfer_safe() {
  127. env e;
  128. uint256 ID;
  129. address a; address b;
  130. require delegates(a) != delegates(b); // confirmed if they both delegate to the same person then transfer keeps the votes the same
  131. require numCheckpoints(delegates(a)) < 1000000;
  132. require numCheckpoints(delegates(b)) < 1000000;
  133. uint256 votesA_pre = getVotes(delegates(a));
  134. uint256 votesB_pre = getVotes(delegates(b));
  135. mathint totalVotes_pre = totalVotes();
  136. transferFrom(e, a, b, ID);
  137. mathint totalVotes_post = totalVotes();
  138. uint256 votesA_post = getVotes(delegates(a));
  139. uint256 votesB_post = getVotes(delegates(b));
  140. // if an account that has not delegated transfers balance to an account that has, it will increase the total supply of votes
  141. assert totalVotes_pre == totalVotes_post, "transfer changed total supply";
  142. assert delegates(a) != 0 => votesA_pre - 1 == votesA_post, "A lost the wrong amount of votes";
  143. assert delegates(b) != 0 => votesB_pre + 1 == votesB_post, "B gained the wrong amount of votes";
  144. }
  145. // for any given function f, if the delegate is changed the function must be delegate or delegateBySig
  146. // passes
  147. rule delegates_safe(method f) filtered {f -> (f.selector != delegate(address).selector &&
  148. f.selector != _delegate(address, address).selector &&
  149. f.selector != delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32).selector) }
  150. {
  151. env e; calldataarg args;
  152. address account;
  153. address pre = delegates(account);
  154. f(e, args);
  155. address post = delegates(account);
  156. assert pre == post, "invalid delegate change";
  157. }
  158. // delegates increases the delegatee's votes by the proper amount
  159. // passes + rule sanity
  160. rule delegatee_receives_votes() {
  161. env e;
  162. address delegator; address delegatee;
  163. require numCheckpoints(delegatee) < 1000000;
  164. require delegates(delegator) != delegatee;
  165. require delegatee != 0x0;
  166. uint256 delegator_bal = balanceOf(e, delegator);
  167. uint256 votes_= getVotes(delegatee);
  168. _delegate(e, delegator, delegatee);
  169. uint256 _votes = getVotes(delegatee);
  170. assert _votes == votes_ + delegator_bal, "delegatee did not receive votes";
  171. }
  172. // passes + rule sanity
  173. rule previous_delegatee_votes_removed() {
  174. env e;
  175. address delegator; address delegatee; address third;
  176. require third != delegatee;
  177. require delegates(delegator) == third;
  178. require numCheckpoints(third) < 1000000;
  179. uint256 delegator_bal = balanceOf(e, delegator);
  180. uint256 votes_ = getVotes(third);
  181. _delegate(e, delegator, delegatee);
  182. uint256 _votes = getVotes(third);
  183. assert third != 0x0 => _votes == votes_ - delegator_bal, "votes not removed from the previous delegatee";
  184. }
  185. // passes with rule sanity
  186. rule delegate_contained() {
  187. env e;
  188. address delegator; address delegatee; address other;
  189. require other != delegatee;
  190. require other != delegates(delegator);
  191. uint256 votes_ = getVotes(other);
  192. _delegate(e, delegator, delegatee);
  193. uint256 _votes = getVotes(other);
  194. assert votes_ == _votes, "votes not contained";
  195. }
  196. rule delegate_no_frontrunning(method f) {
  197. env e; calldataarg args;
  198. address delegator; address delegatee; address third; address other;
  199. require numCheckpoints(delegatee) < 1000000;
  200. require numCheckpoints(third) < 1000000;
  201. f(e, args);
  202. uint256 delegator_bal = balanceOf(e, delegator);
  203. uint256 delegatee_votes_ = getVotes(delegatee);
  204. uint256 third_votes_ = getVotes(third);
  205. uint256 other_votes_ = getVotes(other);
  206. require delegates(delegator) == third;
  207. require third != delegatee;
  208. require other != third;
  209. require other != delegatee;
  210. require delegatee != 0x0;
  211. _delegate(e, delegator, delegatee);
  212. uint256 _delegatee_votes = getVotes(delegatee);
  213. uint256 _third_votes = getVotes(third);
  214. uint256 _other_votes = getVotes(other);
  215. // previous delegatee loses all of their votes
  216. // delegatee gains that many votes
  217. // third loses any votes delegated to them
  218. assert _delegatee_votes == delegatee_votes_ + delegator_bal, "delegatee did not receive votes";
  219. assert third != 0 => _third_votes == third_votes_ - delegator_bal, "votes not removed from third";
  220. assert other_votes_ == _other_votes, "delegate not contained";
  221. }
  222. // mint and burn need to be handled differently for ERC721
  223. // rule mint_increases_totalSupply() {
  224. // env e;
  225. // uint256 amount; address account;
  226. // uint256 fromBlock = e.block.number;
  227. // uint256 totalSupply_ = totalSupply();
  228. // mint(e, account, amount);
  229. // uint256 _totalSupply = totalSupply();
  230. // require _totalSupply < _maxSupply();
  231. // assert _totalSupply == totalSupply_ + amount, "totalSupply not increased properly";
  232. // assert getPastTotalSupply(e, fromBlock) == totalSupply_ , "previous total supply not saved properly";
  233. // }
  234. // rule burn_decreases_totalSupply() {
  235. // env e;
  236. // uint256 amount; address account;
  237. // uint256 fromBlock = e.block.number;
  238. // uint256 totalSupply_ = totalSupply();
  239. // burn(e, account, amount);
  240. // uint256 _totalSupply = totalSupply();
  241. // assert _totalSupply == totalSupply_ - amount, "totalSupply not decreased properly";
  242. // assert getPastTotalSupply(e, fromBlock) == totalSupply_ , "previous total supply not saved properly";
  243. // }
  244. // rule mint_doesnt_increase_totalVotes() {
  245. // env e;
  246. // uint256 amount; address account;
  247. // mathint totalVotes_ = totalVotes();
  248. // mint(e, account, amount);
  249. // assert totalVotes() == totalVotes_, "totalVotes increased";
  250. // }
  251. // rule burn_doesnt_decrease_totalVotes() {
  252. // env e;
  253. // uint256 amount; address account;
  254. // mathint totalVotes_ = totalVotes();
  255. // burn(e, account, amount);
  256. // assert totalVotes() == totalVotes_, "totalVotes decreased";
  257. // }