ERC20Wrapper.spec 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import "helpers/helpers.spec";
  2. import "ERC20.spec";
  3. methods {
  4. function underlying() external returns(address) envfree;
  5. function underlyingTotalSupply() external returns(uint256) envfree;
  6. function underlyingBalanceOf(address) external returns(uint256) envfree;
  7. function underlyingAllowanceToThis(address) external returns(uint256) envfree;
  8. function depositFor(address, uint256) external returns(bool);
  9. function withdrawTo(address, uint256) external returns(bool);
  10. function recover(address) external 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) <= to_mathint(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 != currentContract && // invalid receiver
  75. receiver != 0 && // invalid receiver
  76. amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance
  77. amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance
  78. );
  79. // effects
  80. assert success => (
  81. to_mathint(balanceOf(receiver)) == balanceBefore + amount &&
  82. to_mathint(totalSupply()) == supplyBefore + amount &&
  83. to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore + amount &&
  84. to_mathint(underlyingBalanceOf(sender)) == senderUnderlyingBalanceBefore - amount
  85. );
  86. // no side effect
  87. assert underlyingTotalSupply() == underlyingSupplyBefore;
  88. assert balanceOf(other) != otherBalanceBefore => other == receiver;
  89. assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract);
  90. }
  91. /*
  92. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  93. │ Rule: withdrawTo liveness and effects │
  94. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  95. */
  96. rule withdrawTo(env e) {
  97. require nonpayable(e);
  98. address sender = e.msg.sender;
  99. address receiver;
  100. address other;
  101. uint256 amount;
  102. // sanity
  103. requireInvariant noSelfWrap;
  104. requireInvariant totalSupplyIsSumOfBalances;
  105. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  106. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver);
  107. uint256 balanceBefore = balanceOf(sender);
  108. uint256 supplyBefore = totalSupply();
  109. uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver);
  110. uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
  111. uint256 underlyingSupplyBefore = underlyingTotalSupply();
  112. uint256 otherBalanceBefore = balanceOf(other);
  113. uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
  114. withdrawTo@withrevert(e, receiver, amount);
  115. bool success = !lastReverted;
  116. // liveness
  117. assert success <=> (
  118. sender != 0 && // invalid sender
  119. receiver != currentContract && // invalid receiver
  120. receiver != 0 && // invalid receiver
  121. amount <= balanceBefore // withdraw doesn't exceed balance
  122. );
  123. // effects
  124. assert success => (
  125. to_mathint(balanceOf(sender)) == balanceBefore - amount &&
  126. to_mathint(totalSupply()) == supplyBefore - amount &&
  127. to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) &&
  128. to_mathint(underlyingBalanceOf(receiver)) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0)
  129. );
  130. // no side effect
  131. assert underlyingTotalSupply() == underlyingSupplyBefore;
  132. assert balanceOf(other) != otherBalanceBefore => other == sender;
  133. assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract);
  134. }
  135. /*
  136. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  137. │ Rule: recover liveness and effects │
  138. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  139. */
  140. rule recover(env e) {
  141. require nonpayable(e);
  142. address receiver;
  143. address other;
  144. // sanity
  145. requireInvariant noSelfWrap;
  146. requireInvariant totalSupplyIsSumOfBalances;
  147. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  148. mathint value = underlyingBalanceOf(currentContract) - totalSupply();
  149. uint256 supplyBefore = totalSupply();
  150. uint256 balanceBefore = balanceOf(receiver);
  151. uint256 otherBalanceBefore = balanceOf(other);
  152. uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
  153. recover@withrevert(e, receiver);
  154. bool success = !lastReverted;
  155. // liveness
  156. assert success <=> receiver != 0;
  157. // effect
  158. assert success => (
  159. to_mathint(balanceOf(receiver)) == balanceBefore + value &&
  160. to_mathint(totalSupply()) == supplyBefore + value &&
  161. totalSupply() == underlyingBalanceOf(currentContract)
  162. );
  163. // no side effect
  164. assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore;
  165. assert balanceOf(other) != otherBalanceBefore => other == receiver;
  166. }