ERC1155Burnable.spec 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. methods {
  2. balanceOf(address, uint256) returns uint256 envfree
  3. isApprovedForAll(address,address) returns bool envfree
  4. }
  5. /// If a method call reduces account balances, the caller must be either the
  6. /// holder of the account or approved to act on the holder's behalf.
  7. /// n.b. This rule was passing for all methods except `_burn` and `_burnBatch`,
  8. /// ordinarily internal methods that are callable by our tool only because they
  9. /// were changed to public for the purposes of prior verification. Filtered here
  10. /// since they are not generally callable.
  11. rule onlyHolderOrApprovedCanReduceBalance(method f)
  12. filtered {
  13. f -> f.selector != _burn(address,uint256,uint256).selector
  14. && f.selector != _burnBatch(address,uint256[],uint256[]).selector
  15. }
  16. {
  17. address holder; uint256 token; uint256 amount;
  18. uint256 balanceBefore = balanceOf(holder, token);
  19. env e; calldataarg args;
  20. f(e, args);
  21. uint256 balanceAfter = balanceOf(holder, token);
  22. assert balanceAfter < balanceBefore => e.msg.sender == holder || isApprovedForAll(holder, e.msg.sender),
  23. "An account balance may only be reduced by the holder or a holder-approved agent";
  24. }
  25. /// Burning a larger amount of a token must reduce that token's balance more
  26. /// than burning a smaller amount.
  27. /// n.b. This rule holds for `burnBatch` as well due to rules establishing
  28. /// appropriate equivance between `burn` and `burnBatch` methods.
  29. rule burnAmountProportionalToBalanceReduction {
  30. storage beforeBurn = lastStorage;
  31. env e;
  32. address holder; uint256 token;
  33. mathint startingBalance = balanceOf(holder, token);
  34. uint256 smallBurn; uint256 largeBurn;
  35. require smallBurn < largeBurn;
  36. // smaller burn amount
  37. burn(e, holder, token, smallBurn) at beforeBurn;
  38. mathint smallBurnBalanceChange = startingBalance - balanceOf(holder, token);
  39. // larger burn amount
  40. burn(e, holder, token, largeBurn) at beforeBurn;
  41. mathint largeBurnBalanceChange = startingBalance - balanceOf(holder, token);
  42. assert smallBurnBalanceChange < largeBurnBalanceChange,
  43. "A larger burn must lead to a larger decrease in balance";
  44. }
  45. /// @decription Two sequential burns must be equivalent to a single burn of the sum of their
  46. /// amounts.
  47. /// @formula <pseudocode math description
  48. /// @nota_bene This rule holds for `burnBatch` as well due to rules establishing
  49. /// appropriate equivance between `burn` and `burnBatch` methods.
  50. rule sequentialBurnsEquivalentToSingleBurnOfSum {
  51. storage beforeBurns = lastStorage;
  52. env e;
  53. address holder; uint256 token;
  54. mathint startingBalance = balanceOf(holder, token);
  55. uint256 firstBurn; uint256 secondBurn; uint256 sumBurn;
  56. require sumBurn == firstBurn + secondBurn;
  57. // sequential burns
  58. burn(e, holder, token, firstBurn) at beforeBurns;
  59. burn(e, holder, token, secondBurn);
  60. mathint sequentialBurnsBalanceChange = startingBalance - balanceOf(holder, token);
  61. // burn of sum of sequential burns
  62. burn(e, holder, token, sumBurn) at beforeBurns;
  63. mathint sumBurnBalanceChange = startingBalance - balanceOf(holder, token);
  64. assert sequentialBurnsBalanceChange == sumBurnBalanceChange,
  65. "Sequential burns must be equivalent to a burn of their sum";
  66. }
  67. /// The result of burning a single token must be equivalent whether done via
  68. /// burn or burnBatch.
  69. rule singleTokenBurnBurnBatchEquivalence {
  70. storage beforeBurn = lastStorage;
  71. env e;
  72. address holder;
  73. uint256 token; uint256 burnAmount;
  74. uint256[] tokens; uint256[] burnAmounts;
  75. mathint startingBalance = balanceOf(holder, token);
  76. require tokens.length == 1; require burnAmounts.length == 1;
  77. require tokens[0] == token; require burnAmounts[0] == burnAmount;
  78. // burning via burn
  79. burn(e, holder, token, burnAmount) at beforeBurn;
  80. mathint burnBalanceChange = startingBalance - balanceOf(holder, token);
  81. // burning via burnBatch
  82. burnBatch(e, holder, tokens, burnAmounts) at beforeBurn;
  83. mathint burnBatchBalanceChange = startingBalance - balanceOf(holder, token);
  84. assert burnBalanceChange == burnBatchBalanceChange,
  85. "Burning a single token via burn or burnBatch must be equivalent";
  86. }
  87. /// @description The results of burning multiple tokens must be equivalent whether done
  88. /// separately via burn or together via burnBatch.
  89. rule multipleTokenBurnBurnBatchEquivalence {
  90. storage beforeBurns = lastStorage;
  91. env e;
  92. address holder;
  93. uint256 tokenA; uint256 tokenB; uint256 tokenC;
  94. uint256 burnAmountA; uint256 burnAmountB; uint256 burnAmountC;
  95. uint256[] tokens; uint256[] burnAmounts;
  96. mathint startingBalanceA = balanceOf(holder, tokenA);
  97. mathint startingBalanceB = balanceOf(holder, tokenB);
  98. mathint startingBalanceC = balanceOf(holder, tokenC);
  99. require tokens.length == 3; require burnAmounts.length == 3;
  100. require tokens[0] == tokenA; require burnAmounts[0] == burnAmountA;
  101. require tokens[1] == tokenB; require burnAmounts[1] == burnAmountB;
  102. require tokens[2] == tokenC; require burnAmounts[2] == burnAmountC;
  103. // burning via burn
  104. burn(e, holder, tokenA, burnAmountA) at beforeBurns;
  105. burn(e, holder, tokenB, burnAmountB);
  106. burn(e, holder, tokenC, burnAmountC);
  107. mathint burnBalanceChangeA = startingBalanceA - balanceOf(holder, tokenA);
  108. mathint burnBalanceChangeB = startingBalanceB - balanceOf(holder, tokenB);
  109. mathint burnBalanceChangeC = startingBalanceC - balanceOf(holder, tokenC);
  110. // burning via burnBatch
  111. burnBatch(e, holder, tokens, burnAmounts) at beforeBurns;
  112. mathint burnBatchBalanceChangeA = startingBalanceA - balanceOf(holder, tokenA);
  113. mathint burnBatchBalanceChangeB = startingBalanceB - balanceOf(holder, tokenB);
  114. mathint burnBatchBalanceChangeC = startingBalanceC - balanceOf(holder, tokenC);
  115. assert burnBalanceChangeA == burnBatchBalanceChangeA
  116. && burnBalanceChangeB == burnBatchBalanceChangeB
  117. && burnBalanceChangeC == burnBatchBalanceChangeC,
  118. "Burning multiple tokens via burn or burnBatch must be equivalent";
  119. }
  120. /// If passed empty token and burn amount arrays, burnBatch must not change
  121. /// token balances or address permissions.
  122. rule burnBatchOnEmptyArraysChangesNothing {
  123. env e;
  124. address holder; uint256 token;
  125. address nonHolderA; address nonHolderB;
  126. uint256 startingBalance = balanceOf(holder, token);
  127. bool startingPermissionNonHolderA = isApprovedForAll(holder, nonHolderA);
  128. bool startingPermissionNonHolderB = isApprovedForAll(holder, nonHolderB);
  129. uint256[] noTokens; uint256[] noBurnAmounts;
  130. require noTokens.length == 0; require noBurnAmounts.length == 0;
  131. burnBatch(e, holder, noTokens, noBurnAmounts);
  132. uint256 endingBalance = balanceOf(holder, token);
  133. bool endingPermissionNonHolderA = isApprovedForAll(holder, nonHolderA);
  134. bool endingPermissionNonHolderB = isApprovedForAll(holder, nonHolderB);
  135. assert startingBalance == endingBalance,
  136. "burnBatch must not change token balances if passed empty arrays";
  137. assert startingPermissionNonHolderA == endingPermissionNonHolderA
  138. && startingPermissionNonHolderB == endingPermissionNonHolderB,
  139. "burnBatch must not change account permissions if passed empty arrays";
  140. }
  141. /// This rule should always fail.
  142. rule sanity {
  143. method f; env e; calldataarg args;
  144. f(e, args);
  145. assert false,
  146. "This rule should always fail";
  147. }