ERC20.spec 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import "interfaces/erc20.spec"
  2. /*
  3. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  4. │ Ghost & hooks: sum of all balances │
  5. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  6. */
  7. ghost sumOfBalances() returns uint256 {
  8. init_state axiom sumOfBalances() == 0;
  9. }
  10. hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE {
  11. havoc sumOfBalances assuming sumOfBalances@new() == sumOfBalances@old() + newValue - oldValue;
  12. }
  13. /*
  14. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  15. │ Invariant: totalSupply is the sum of all balances │
  16. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  17. */
  18. invariant totalSupplyIsSumOfBalances()
  19. totalSupply() == sumOfBalances()
  20. /*
  21. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  22. │ Invariant: balance of address(0) is 0 │
  23. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  24. */
  25. invariant zeroAddressNoBalance()
  26. balanceOf(0) == 0
  27. /*
  28. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  29. │ Rules: totalSupply only changes through mint or burn │
  30. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  31. */
  32. rule noChangeTotalSupply() {
  33. requireInvariant totalSupplyIsSumOfBalances();
  34. env e;
  35. method f;
  36. calldataarg args;
  37. uint256 totalSupplyBefore = totalSupply();
  38. f(e, args);
  39. uint256 totalSupplyAfter = totalSupply();
  40. assert (totalSupplyAfter > totalSupplyBefore) => (f.selector == _mint(address,uint256).selector);
  41. assert (totalSupplyAfter < totalSupplyBefore) => (f.selector == _burn(address,uint256).selector);
  42. }
  43. /*
  44. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  45. │ Rules: totalSupply change matches minted or burned amount │
  46. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  47. */
  48. rule mintIncreasesTotalSupply() {
  49. requireInvariant totalSupplyIsSumOfBalances();
  50. env e;
  51. address to;
  52. uint256 amount;
  53. uint256 totalSupplyBefore = totalSupply();
  54. _mint(e, to, amount);
  55. uint256 totalSupplyAfter = totalSupply();
  56. assert totalSupplyAfter == totalSupplyBefore + amount;
  57. }
  58. rule burnDecreasesTotalSupply() {
  59. requireInvariant totalSupplyIsSumOfBalances();
  60. env e;
  61. address from;
  62. uint256 amount;
  63. uint256 totalSupplyBefore = totalSupply();
  64. _burn(e, from, amount);
  65. uint256 totalSupplyAfter = totalSupply();
  66. assert totalSupplyAfter == totalSupplyBefore - amount;
  67. }
  68. /*
  69. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  70. │ Rules: Balance can only decrease if the tx was sent by holder or by approved spender │
  71. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  72. */
  73. rule onlyAuthorizedCanTransfer() {
  74. requireInvariant totalSupplyIsSumOfBalances();
  75. env e;
  76. method f;
  77. calldataarg args;
  78. address account;
  79. uint256 allowanceBefore = allowance(account, e.msg.sender);
  80. uint256 balanceBefore = balanceOf(account);
  81. f(e, args);
  82. uint256 balanceAfter = balanceOf(account);
  83. assert (
  84. balanceAfter < balanceBefore
  85. ) => (
  86. f.selector == _burn(address,uint256).selector ||
  87. e.msg.sender == account ||
  88. balanceBefore - balanceAfter <= allowanceBefore
  89. );
  90. }
  91. /*
  92. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  93. │ Rules: Allowance can only change if holder calls approve or spender uses allowance │
  94. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  95. */
  96. rule onlyHolderOfSpenderCanChangeAllowance() {
  97. requireInvariant totalSupplyIsSumOfBalances();
  98. env e;
  99. method f;
  100. calldataarg args;
  101. address holder;
  102. address spender;
  103. uint256 allowanceBefore = allowance(holder, spender);
  104. f(e, args);
  105. uint256 allowanceAfter = allowance(holder, spender);
  106. assert (
  107. allowanceAfter > allowanceBefore
  108. ) => (
  109. (f.selector == approve(address,uint256).selector && e.msg.sender == holder) ||
  110. (f.selector == increaseAllowance(address,uint256).selector && e.msg.sender == holder) ||
  111. (f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
  112. );
  113. assert (
  114. allowanceAfter < allowanceBefore
  115. ) => (
  116. (f.selector == approve(address,uint256).selector && e.msg.sender == holder ) ||
  117. (f.selector == decreaseAllowance(address,uint256).selector && e.msg.sender == holder ) ||
  118. (f.selector == transferFrom(address,address,uint256).selector && e.msg.sender == spender) ||
  119. (f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
  120. );
  121. }
  122. /*
  123. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  124. │ Rule: transfer moves correct amount from sender to receiver │
  125. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  126. */
  127. rule transfer() {
  128. requireInvariant totalSupplyIsSumOfBalances();
  129. env e;
  130. address holder = e.msg.sender;
  131. address recipient;
  132. address other;
  133. uint256 amount;
  134. // env: function is not payable
  135. require e.msg.sender != 0;
  136. require e.msg.value == 0;
  137. // cache state
  138. uint256 holderBalanceBefore = balanceOf(holder);
  139. uint256 recipientBalanceBefore = balanceOf(recipient);
  140. uint256 otherBalanceBefore = balanceOf(other);
  141. // run transaction
  142. transfer@withrevert(e, recipient, amount);
  143. // check outcome
  144. if (lastReverted) {
  145. assert holder == 0 || recipient == 0 || amount > holderBalanceBefore;
  146. } else {
  147. // balances of holder and recipient are updated
  148. assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
  149. assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
  150. // no other balance is modified
  151. assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
  152. }
  153. }
  154. /*
  155. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  156. │ Rule: transferFrom moves correct amount from holder to receiver and updates allowance │
  157. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  158. */
  159. rule transferFrom() {
  160. requireInvariant totalSupplyIsSumOfBalances();
  161. env e;
  162. address holder;
  163. address recipient;
  164. address other;
  165. uint256 amount;
  166. // env: function is not payable
  167. require e.msg.sender != 0;
  168. require e.msg.value == 0;
  169. // cache state
  170. uint256 allowanceBefore = allowance(holder, e.msg.sender);
  171. uint256 holderBalanceBefore = balanceOf(holder);
  172. uint256 recipientBalanceBefore = balanceOf(recipient);
  173. uint256 otherBalanceBefore = balanceOf(other);
  174. // run transaction
  175. transferFrom@withrevert(e, holder, recipient, amount);
  176. // check outcome
  177. if (lastReverted) {
  178. assert holder == 0 || recipient == 0 || amount > holderBalanceBefore || amount > allowanceBefore;
  179. } else {
  180. // allowance is valid & updated
  181. assert allowanceBefore >= amount;
  182. assert allowance(holder, e.msg.sender) == (allowanceBefore == max_uint256 ? to_uint256(max_uint256) : allowanceBefore - amount);
  183. // balances of holder and recipient are updated
  184. assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
  185. assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
  186. // no other balance is modified
  187. assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
  188. }
  189. }
  190. /*
  191. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  192. │ Rule: approve sets allowance │
  193. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  194. */
  195. rule approve() {
  196. requireInvariant totalSupplyIsSumOfBalances();
  197. env e;
  198. address holder = e.msg.sender;
  199. address spender;
  200. address otherHolder;
  201. address otherSpender;
  202. uint256 amount;
  203. // env: function is not payable
  204. require e.msg.sender != 0;
  205. require e.msg.value == 0;
  206. // cache state
  207. uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
  208. // run transaction
  209. approve@withrevert(e, spender, amount);
  210. // check outcome
  211. if (lastReverted) {
  212. assert spender == 0;
  213. } else {
  214. // allowance is updated
  215. assert allowance(holder, spender) == amount;
  216. // other allowances are untouched
  217. assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
  218. }
  219. }
  220. /*
  221. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  222. │ Rule: calling increaseAllowance increases the allowance │
  223. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  224. */
  225. rule increaseAllowance() {
  226. requireInvariant totalSupplyIsSumOfBalances();
  227. env e;
  228. address holder = e.msg.sender;
  229. address spender;
  230. address otherHolder;
  231. address otherSpender;
  232. uint256 amount;
  233. // env: function is not payable
  234. require e.msg.sender != 0;
  235. require e.msg.value == 0;
  236. // cache state
  237. uint256 allowanceBefore = allowance(holder, spender);
  238. uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
  239. // run transaction
  240. increaseAllowance@withrevert(e, spender, amount);
  241. // check outcome
  242. if (lastReverted) {
  243. assert spender == 0 || allowanceBefore + amount > max_uint256;
  244. } else {
  245. // allowance is updated
  246. assert allowance(holder, spender) == allowanceBefore + amount;
  247. // other allowances are untouched
  248. assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
  249. }
  250. }
  251. /*
  252. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  253. │ Rule: calling decreaseAllowance decreases the allowance │
  254. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  255. */
  256. rule decreaseAllowance() {
  257. requireInvariant totalSupplyIsSumOfBalances();
  258. env e;
  259. address holder = e.msg.sender;
  260. address spender;
  261. address otherHolder;
  262. address otherSpender;
  263. uint256 amount;
  264. // env: function is not payable
  265. require e.msg.sender != 0;
  266. require e.msg.value == 0;
  267. // cache state
  268. uint256 allowanceBefore = allowance(holder, spender);
  269. uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
  270. // run transaction
  271. decreaseAllowance@withrevert(e, spender, amount);
  272. // check outcome
  273. if (lastReverted) {
  274. assert spender == 0 || allowanceBefore < amount;
  275. } else {
  276. // allowance is updated
  277. assert allowance(holder, spender) == allowanceBefore - amount;
  278. // other allowances are untouched
  279. assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
  280. }
  281. }