ERC20Wrapper.spec 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. definition underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool =
  19. underlyingBalanceOf(a) <= underlyingTotalSupply();
  20. definition sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool =
  21. a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= to_mathint(underlyingTotalSupply());
  22. /*
  23. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  24. │ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) │
  25. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  26. */
  27. invariant totalSupplyIsSmallerThanUnderlyingBalance()
  28. totalSupply() <= underlyingBalanceOf(currentContract) &&
  29. underlyingBalanceOf(currentContract) <= underlyingTotalSupply() &&
  30. underlyingTotalSupply() <= max_uint256
  31. {
  32. preserved {
  33. requireInvariant totalSupplyIsSumOfBalances;
  34. require underlyingBalancesLowerThanUnderlyingSupply(currentContract);
  35. }
  36. preserved depositFor(address account, uint256 amount) with (env e) {
  37. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract);
  38. }
  39. }
  40. invariant noSelfWrap()
  41. currentContract != underlying();
  42. /*
  43. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  44. │ Rule: depositFor liveness and effects │
  45. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  46. */
  47. rule depositFor(env e) {
  48. require nonpayable(e);
  49. address sender = e.msg.sender;
  50. address receiver;
  51. address other;
  52. uint256 amount;
  53. // sanity
  54. requireInvariant noSelfWrap;
  55. requireInvariant totalSupplyIsSumOfBalances;
  56. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  57. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender);
  58. uint256 balanceBefore = balanceOf(receiver);
  59. uint256 supplyBefore = totalSupply();
  60. uint256 senderUnderlyingBalanceBefore = underlyingBalanceOf(sender);
  61. uint256 senderUnderlyingAllowanceBefore = underlyingAllowanceToThis(sender);
  62. uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
  63. uint256 underlyingSupplyBefore = underlyingTotalSupply();
  64. uint256 otherBalanceBefore = balanceOf(other);
  65. uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
  66. depositFor@withrevert(e, receiver, amount);
  67. bool success = !lastReverted;
  68. // liveness
  69. assert success <=> (
  70. sender != currentContract && // invalid sender
  71. sender != 0 && // invalid sender
  72. receiver != currentContract && // invalid receiver
  73. receiver != 0 && // invalid receiver
  74. amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance
  75. amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance
  76. );
  77. // effects
  78. assert success => (
  79. to_mathint(balanceOf(receiver)) == balanceBefore + amount &&
  80. to_mathint(totalSupply()) == supplyBefore + amount &&
  81. to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore + amount &&
  82. to_mathint(underlyingBalanceOf(sender)) == senderUnderlyingBalanceBefore - amount
  83. );
  84. // no side effect
  85. assert underlyingTotalSupply() == underlyingSupplyBefore;
  86. assert balanceOf(other) != otherBalanceBefore => other == receiver;
  87. assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract);
  88. }
  89. /*
  90. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  91. │ Rule: withdrawTo liveness and effects │
  92. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  93. */
  94. rule withdrawTo(env e) {
  95. require nonpayable(e);
  96. address sender = e.msg.sender;
  97. address receiver;
  98. address other;
  99. uint256 amount;
  100. // sanity
  101. requireInvariant noSelfWrap;
  102. requireInvariant totalSupplyIsSumOfBalances;
  103. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  104. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver);
  105. uint256 balanceBefore = balanceOf(sender);
  106. uint256 supplyBefore = totalSupply();
  107. uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver);
  108. uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
  109. uint256 underlyingSupplyBefore = underlyingTotalSupply();
  110. uint256 otherBalanceBefore = balanceOf(other);
  111. uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
  112. withdrawTo@withrevert(e, receiver, amount);
  113. bool success = !lastReverted;
  114. // liveness
  115. assert success <=> (
  116. sender != 0 && // invalid sender
  117. receiver != currentContract && // invalid receiver
  118. receiver != 0 && // invalid receiver
  119. amount <= balanceBefore // withdraw doesn't exceed balance
  120. );
  121. // effects
  122. assert success => (
  123. to_mathint(balanceOf(sender)) == balanceBefore - amount &&
  124. to_mathint(totalSupply()) == supplyBefore - amount &&
  125. to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) &&
  126. to_mathint(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. mathint 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. to_mathint(balanceOf(receiver)) == balanceBefore + value &&
  158. to_mathint(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. }