ERC20Wrapper.spec 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import "helpers/helpers.spec"
  2. import "ERC20.spec"
  3. methods {
  4. underlying() returns(address) envfree
  5. underlyingTotalSupply() returns(uint256) envfree
  6. underlyingBalanceOf(address) returns(uint256) envfree
  7. underlyingAllowanceToThis(address) returns(uint256) envfree
  8. depositFor(address, uint256) returns(bool)
  9. withdrawTo(address, uint256) returns(bool)
  10. recover(address) returns(uint256)
  11. }
  12. use invariant totalSupplyIsSumOfBalances
  13. /*
  14. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  15. โ”‚ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying โ”‚
  16. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  17. */
  18. function underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool {
  19. return underlyingBalanceOf(a) <= underlyingTotalSupply();
  20. }
  21. function sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool {
  22. return a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= underlyingTotalSupply();
  23. }
  24. /*
  25. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  26. โ”‚ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) โ”‚
  27. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  28. */
  29. invariant totalSupplyIsSmallerThanUnderlyingBalance()
  30. totalSupply() <= underlyingBalanceOf(currentContract) &&
  31. underlyingBalanceOf(currentContract) <= underlyingTotalSupply() &&
  32. underlyingTotalSupply() <= max_uint256
  33. {
  34. preserved {
  35. requireInvariant totalSupplyIsSumOfBalances;
  36. require underlyingBalancesLowerThanUnderlyingSupply(currentContract);
  37. }
  38. preserved depositFor(address account, uint256 amount) with (env e) {
  39. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract);
  40. }
  41. }
  42. invariant noSelfWrap()
  43. currentContract != underlying()
  44. /*
  45. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  46. โ”‚ Rule: depositFor liveness and effects โ”‚
  47. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  48. */
  49. rule depositFor(env e) {
  50. require nonpayable(e);
  51. address sender = e.msg.sender;
  52. address receiver;
  53. address other;
  54. uint256 amount;
  55. // sanity
  56. requireInvariant noSelfWrap;
  57. requireInvariant totalSupplyIsSumOfBalances;
  58. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  59. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender);
  60. uint256 balanceBefore = balanceOf(receiver);
  61. uint256 supplyBefore = totalSupply();
  62. uint256 senderUnderlyingBalanceBefore = underlyingBalanceOf(sender);
  63. uint256 senderUnderlyingAllowanceBefore = underlyingAllowanceToThis(sender);
  64. uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
  65. uint256 underlyingSupplyBefore = underlyingTotalSupply();
  66. uint256 otherBalanceBefore = balanceOf(other);
  67. uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
  68. depositFor@withrevert(e, receiver, amount);
  69. bool success = !lastReverted;
  70. // liveness
  71. assert success <=> (
  72. sender != currentContract && // invalid sender
  73. sender != 0 && // invalid sender
  74. receiver != 0 && // invalid receiver
  75. amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance
  76. amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance
  77. );
  78. // effects
  79. assert success => (
  80. balanceOf(receiver) == balanceBefore + amount &&
  81. totalSupply() == supplyBefore + amount &&
  82. underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore + amount &&
  83. underlyingBalanceOf(sender) == senderUnderlyingBalanceBefore - amount
  84. );
  85. // no side effect
  86. assert underlyingTotalSupply() == underlyingSupplyBefore;
  87. assert balanceOf(other) != otherBalanceBefore => other == receiver;
  88. assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract);
  89. }
  90. /*
  91. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  92. โ”‚ Rule: withdrawTo liveness and effects โ”‚
  93. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  94. */
  95. rule withdrawTo(env e) {
  96. require nonpayable(e);
  97. address sender = e.msg.sender;
  98. address receiver;
  99. address other;
  100. uint256 amount;
  101. // sanity
  102. requireInvariant noSelfWrap;
  103. requireInvariant totalSupplyIsSumOfBalances;
  104. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  105. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver);
  106. uint256 balanceBefore = balanceOf(sender);
  107. uint256 supplyBefore = totalSupply();
  108. uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver);
  109. uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
  110. uint256 underlyingSupplyBefore = underlyingTotalSupply();
  111. uint256 otherBalanceBefore = balanceOf(other);
  112. uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
  113. withdrawTo@withrevert(e, receiver, amount);
  114. bool success = !lastReverted;
  115. // liveness
  116. assert success <=> (
  117. sender != 0 && // invalid sender
  118. receiver != 0 && // invalid receiver
  119. amount <= balanceBefore // withdraw doesn't exceed balance
  120. );
  121. // effects
  122. assert success => (
  123. balanceOf(sender) == balanceBefore - amount &&
  124. totalSupply() == supplyBefore - amount &&
  125. underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) &&
  126. underlyingBalanceOf(receiver) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0)
  127. );
  128. // no side effect
  129. assert underlyingTotalSupply() == underlyingSupplyBefore;
  130. assert balanceOf(other) != otherBalanceBefore => other == sender;
  131. assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract);
  132. }
  133. /*
  134. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  135. โ”‚ Rule: recover liveness and effects โ”‚
  136. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  137. */
  138. rule recover(env e) {
  139. require nonpayable(e);
  140. address receiver;
  141. address other;
  142. // sanity
  143. requireInvariant noSelfWrap;
  144. requireInvariant totalSupplyIsSumOfBalances;
  145. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  146. uint256 value = underlyingBalanceOf(currentContract) - totalSupply();
  147. uint256 supplyBefore = totalSupply();
  148. uint256 balanceBefore = balanceOf(receiver);
  149. uint256 otherBalanceBefore = balanceOf(other);
  150. uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
  151. recover@withrevert(e, receiver);
  152. bool success = !lastReverted;
  153. // liveness
  154. assert success <=> receiver != 0;
  155. // effect
  156. assert success => (
  157. balanceOf(receiver) == balanceBefore + value &&
  158. totalSupply() == supplyBefore + value &&
  159. totalSupply() == underlyingBalanceOf(currentContract)
  160. );
  161. // no side effect
  162. assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore;
  163. assert balanceOf(other) != otherBalanceBefore => other == receiver;
  164. }