ERC1155Burnable.spec 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 is 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 verification.
  10. rule onlyHolderOrApprovedCanReduceBalance {
  11. address holder; uint256 token; uint256 amount;
  12. uint256 balanceBefore = balanceOf(holder, token);
  13. method f; env e; calldataarg args;
  14. f(e, args);
  15. uint256 balanceAfter = balanceOf(holder, token);
  16. assert balanceAfter < balanceBefore => e.msg.sender == holder || isApprovedForAll(holder, e.msg.sender),
  17. "An account balance may only be reduced by the holder or a holder-approved agent";
  18. }
  19. /// Burning a larger amount of a token must reduce that token's balance more
  20. /// than burning a smaller amount.
  21. /// n.b. This rule holds for `burnBatch` as well due to rules establishing
  22. /// appropriate equivance between `burn` and `burnBatch` methods.
  23. rule burnAmountProportionalToBalanceReduction {
  24. storage beforeBurn = lastStorage;
  25. env e;
  26. address holder; uint256 token;
  27. mathint startingBalance = balanceOf(holder, token);
  28. uint256 smallBurn; uint256 largeBurn;
  29. require smallBurn < largeBurn;
  30. // smaller burn amount
  31. burn(e, holder, token, smallBurn) at beforeBurn;
  32. mathint smallBurnBalanceChange = startingBalance - balanceOf(holder, token);
  33. // larger burn amount
  34. burn(e, holder, token, largeBurn) at beforeBurn;
  35. mathint largeBurnBalanceChange = startingBalance - balanceOf(holder, token);
  36. assert smallBurnBalanceChange < largeBurnBalanceChange,
  37. "A larger burn must lead to a larger decrease in balance";
  38. }
  39. /// This rule not needed (because multipleTokenBurnBurnBatchEquivalence)
  40. /// Unimplemented rule to verify monotonicity of burnBatch.
  41. /// Using only burnBatch, possible approach:
  42. /// Token with smaller and larger burn amounts
  43. /// Round one smaller burn
  44. /// Round two larger burn
  45. rule burnBatchAmountProportionalToBalanceReduction { // TODO implement rule or remove
  46. storage beforeBurn = lastStorage;
  47. env e;
  48. address holder; uint256 token;
  49. mathint startingBalance = balanceOf(holder, token);
  50. uint256 smallBurn; uint256 largeBurn;
  51. require smallBurn < largeBurn;
  52. uint256[] tokens; uint256[] smallBurnAmounts; uint256[] largeBurnAmounts;
  53. require tokens.length == 1; require smallBurnAmounts.length == 1; require largeBurnAmounts.length == 1;
  54. require tokens[0] == token; require smallBurnAmounts[0] == smallBurn; require largeBurnAmounts[0] == largeBurn;
  55. // smaller burn amount
  56. burnBatch(e, holder, tokens, smallBurnAmounts) at beforeBurn;
  57. mathint smallBurnBalanceChange = startingBalance - balanceOf(holder, token);
  58. // larger burn amount
  59. burnBatch(e, holder, tokens, largeBurnAmounts) at beforeBurn;
  60. mathint largeBurnBalanceChange = startingBalance - balanceOf(holder, token);
  61. assert smallBurnBalanceChange < largeBurnBalanceChange,
  62. "A larger burn must lead to a larger decrease in balance";
  63. }
  64. /// Two sequential burns must be equivalent to a single burn of the sum of their
  65. /// amounts.
  66. /// n.b. This rule holds for `burnBatch` as well due to rules establishing
  67. /// appropriate equivance between `burn` and `burnBatch` methods.
  68. rule sequentialBurnsEquivalentToSingleBurnOfSum {
  69. storage beforeBurns = lastStorage;
  70. env e;
  71. address holder; uint256 token;
  72. mathint startingBalance = balanceOf(holder, token);
  73. uint256 firstBurn; uint256 secondBurn; uint256 sumBurn;
  74. require sumBurn == firstBurn + secondBurn;
  75. // sequential burns
  76. burn(e, holder, token, firstBurn) at beforeBurns;
  77. burn(e, holder, token, secondBurn);
  78. mathint sequentialBurnsBalanceChange = startingBalance - balanceOf(holder, token);
  79. // burn of sum of sequential burns
  80. burn(e, holder, token, sumBurn) at beforeBurns;
  81. mathint sumBurnBalanceChange = startingBalance - balanceOf(holder, token);
  82. assert sequentialBurnsBalanceChange == sumBurnBalanceChange,
  83. "Sequential burns must be equivalent to a burn of their sum";
  84. }
  85. /// The result of burning a single token must be equivalent whether done via
  86. /// burn or burnBatch.
  87. rule singleTokenBurnBurnBatchEquivalence {
  88. storage beforeBurn = lastStorage;
  89. env e;
  90. address holder;
  91. uint256 token; uint256 burnAmount;
  92. uint256[] tokens; uint256[] burnAmounts;
  93. mathint startingBalance = balanceOf(holder, token);
  94. require tokens.length == 1; require burnAmounts.length == 1;
  95. require tokens[0] == token; require burnAmounts[0] == burnAmount;
  96. // burning via burn
  97. burn(e, holder, token, burnAmount) at beforeBurn;
  98. mathint burnBalanceChange = startingBalance - balanceOf(holder, token);
  99. // burning via burnBatch
  100. burnBatch(e, holder, tokens, burnAmounts) at beforeBurn;
  101. mathint burnBatchBalanceChange = startingBalance - balanceOf(holder, token);
  102. assert burnBalanceChange == burnBatchBalanceChange,
  103. "Burning a single token via burn or burnBatch must be equivalent";
  104. }
  105. /// The results of burning multiple tokens must be equivalent whether done
  106. /// separately via burn or together via burnBatch.
  107. rule multipleTokenBurnBurnBatchEquivalence {
  108. storage beforeBurns = lastStorage;
  109. env e;
  110. address holder;
  111. uint256 tokenA; uint256 tokenB; uint256 tokenC;
  112. uint256 burnAmountA; uint256 burnAmountB; uint256 burnAmountC;
  113. uint256[] tokens; uint256[] burnAmounts;
  114. mathint startingBalanceA = balanceOf(holder, tokenA);
  115. mathint startingBalanceB = balanceOf(holder, tokenB);
  116. mathint startingBalanceC = balanceOf(holder, tokenC);
  117. require tokens.length == 3; require burnAmounts.length == 3;
  118. require tokens[0] == tokenA; require burnAmounts[0] == burnAmountA;
  119. require tokens[1] == tokenB; require burnAmounts[1] == burnAmountB;
  120. require tokens[2] == tokenC; require burnAmounts[2] == burnAmountC;
  121. // burning via burn
  122. burn(e, holder, tokenA, burnAmountA) at beforeBurns;
  123. burn(e, holder, tokenB, burnAmountB);
  124. burn(e, holder, tokenC, burnAmountC);
  125. mathint burnBalanceChangeA = startingBalanceA - balanceOf(holder, tokenA);
  126. mathint burnBalanceChangeB = startingBalanceB - balanceOf(holder, tokenB);
  127. mathint burnBalanceChangeC = startingBalanceC - balanceOf(holder, tokenC);
  128. // burning via burnBatch
  129. burnBatch(e, holder, tokens, burnAmounts) at beforeBurns;
  130. mathint burnBatchBalanceChangeA = startingBalanceA - balanceOf(holder, tokenA);
  131. mathint burnBatchBalanceChangeB = startingBalanceB - balanceOf(holder, tokenB);
  132. mathint burnBatchBalanceChangeC = startingBalanceC - balanceOf(holder, tokenC);
  133. assert burnBalanceChangeA == burnBatchBalanceChangeA
  134. && burnBalanceChangeB == burnBatchBalanceChangeB
  135. && burnBalanceChangeC == burnBatchBalanceChangeC,
  136. "Burning multiple tokens via burn or burnBatch must be equivalent";
  137. }
  138. /// If passed empty token and burn amount arrays, burnBatch must not change
  139. /// token balances or address permissions.
  140. rule burnBatchOnEmptyArraysChangesNothing {
  141. env e;
  142. address holder; uint256 token;
  143. address nonHolderA; address nonHolderB;
  144. uint256 startingBalance = balanceOf(holder, token);
  145. bool startingPermissionNonHolderA = isApprovedForAll(holder, nonHolderA);
  146. bool startingPermissionNonHolderB = isApprovedForAll(holder, nonHolderB);
  147. uint256[] noTokens; uint256[] noBurnAmounts;
  148. require noTokens.length == 0; require noBurnAmounts.length == 0;
  149. burnBatch(e, holder, noTokens, noBurnAmounts);
  150. uint256 endingBalance = balanceOf(holder, token);
  151. bool endingPermissionNonHolderA = isApprovedForAll(holder, nonHolderA);
  152. bool endingPermissionNonHolderB = isApprovedForAll(holder, nonHolderB);
  153. assert startingBalance == endingBalance,
  154. "burnBatch must not change token balances if passed empty arrays";
  155. assert startingPermissionNonHolderA == endingPermissionNonHolderA
  156. && startingPermissionNonHolderB == endingPermissionNonHolderB,
  157. "burnBatch must not change account permissions if passed empty arrays";
  158. }
  159. /// This rule should always fail.
  160. rule sanity {
  161. method f; env e; calldataarg args;
  162. f(e, args);
  163. assert false,
  164. "This rule should always fail";
  165. }