ERC20Wrapper.spec 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import "helpers/helpers.spec";
  2. import "ERC20.spec";
  3. using ERC20PermitHarness as underlying;
  4. methods {
  5. function underlying() external returns(address) envfree;
  6. function depositFor(address, uint256) external returns(bool);
  7. function withdrawTo(address, uint256) external returns(bool);
  8. function recover(address) external returns(uint256);
  9. function underlying.totalSupply() external returns (uint256) envfree;
  10. function underlying.balanceOf(address) external returns (uint256) envfree;
  11. function underlying.allowance(address,address) external returns (uint256) envfree;
  12. unresolved external in _._ => DISPATCH(optimistic=true) [
  13. underlying.transferFrom(address, address, uint256),
  14. underlying.transfer(address, uint256)
  15. ];
  16. }
  17. use invariant totalSupplyIsSumOfBalances;
  18. /*
  19. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  20. │ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying │
  21. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  22. */
  23. definition sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool =
  24. a != b => underlying.balanceOf(a) + underlying.balanceOf(b) <= to_mathint(underlying.totalSupply());
  25. /*
  26. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  27. │ Invariant: wrapped token should not allow any third party to spend its tokens │
  28. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  29. */
  30. invariant noAllowance(address user)
  31. underlying.allowance(currentContract, user) == 0
  32. {
  33. preserved ERC20PermitHarness.approve(address spender, uint256 value) with (env e) {
  34. require e.msg.sender != currentContract;
  35. }
  36. preserved ERC20PermitHarness.permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) with (env e) {
  37. require owner != currentContract;
  38. }
  39. }
  40. /*
  41. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  42. │ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) │
  43. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  44. */
  45. invariant totalSupplyIsSmallerThanUnderlyingBalance()
  46. totalSupply() <= underlying.balanceOf(currentContract) &&
  47. underlying.balanceOf(currentContract) <= underlying.totalSupply() &&
  48. underlying.totalSupply() <= max_uint256
  49. {
  50. preserved with (env e) {
  51. requireInvariant totalSupplyIsSumOfBalances;
  52. require e.msg.sender != currentContract;
  53. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract);
  54. }
  55. preserved ERC20PermitHarness.transferFrom(address from, address to, uint256 amount) with (env e) {
  56. requireInvariant noAllowance(e.msg.sender);
  57. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(from, to);
  58. }
  59. preserved ERC20PermitHarness.burn(address from, uint256 amount) with (env e) {
  60. // If someone can burn from the wrapper, than the invariant obviously doesn't hold.
  61. require from != currentContract;
  62. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(from, currentContract);
  63. }
  64. }
  65. rule noSelfWrap() {
  66. assert currentContract != underlying();
  67. }
  68. /*
  69. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  70. │ Rule: depositFor liveness and effects │
  71. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  72. */
  73. rule depositFor(env e) {
  74. require nonpayable(e);
  75. address sender = e.msg.sender;
  76. address receiver;
  77. address other;
  78. uint256 amount;
  79. // sanity
  80. require currentContract != underlying();
  81. requireInvariant totalSupplyIsSumOfBalances;
  82. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  83. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender);
  84. uint256 balanceBefore = balanceOf(receiver);
  85. uint256 supplyBefore = totalSupply();
  86. uint256 senderUnderlyingBalanceBefore = underlying.balanceOf(sender);
  87. uint256 senderUnderlyingAllowanceBefore = underlying.allowance(sender, currentContract);
  88. uint256 wrapperUnderlyingBalanceBefore = underlying.balanceOf(currentContract);
  89. uint256 underlyingSupplyBefore = underlying.totalSupply();
  90. uint256 otherBalanceBefore = balanceOf(other);
  91. uint256 otherUnderlyingBalanceBefore = underlying.balanceOf(other);
  92. depositFor@withrevert(e, receiver, amount);
  93. bool success = !lastReverted;
  94. // liveness
  95. assert success <=> (
  96. sender != currentContract && // invalid sender
  97. sender != 0 && // invalid sender
  98. receiver != currentContract && // invalid receiver
  99. receiver != 0 && // invalid receiver
  100. amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance
  101. amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance
  102. );
  103. // effects
  104. assert success => (
  105. to_mathint(balanceOf(receiver)) == balanceBefore + amount &&
  106. to_mathint(totalSupply()) == supplyBefore + amount &&
  107. to_mathint(underlying.balanceOf(currentContract)) == wrapperUnderlyingBalanceBefore + amount &&
  108. to_mathint(underlying.balanceOf(sender)) == senderUnderlyingBalanceBefore - amount
  109. );
  110. // no side effect
  111. assert underlying.totalSupply() == underlyingSupplyBefore;
  112. assert balanceOf(other) != otherBalanceBefore => other == receiver;
  113. assert underlying.balanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract);
  114. }
  115. /*
  116. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  117. │ Rule: withdrawTo liveness and effects │
  118. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  119. */
  120. rule withdrawTo(env e) {
  121. require nonpayable(e);
  122. address sender = e.msg.sender;
  123. address receiver;
  124. address other;
  125. uint256 amount;
  126. // sanity
  127. require currentContract != underlying();
  128. requireInvariant totalSupplyIsSumOfBalances;
  129. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  130. require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver);
  131. uint256 balanceBefore = balanceOf(sender);
  132. uint256 supplyBefore = totalSupply();
  133. uint256 receiverUnderlyingBalanceBefore = underlying.balanceOf(receiver);
  134. uint256 wrapperUnderlyingBalanceBefore = underlying.balanceOf(currentContract);
  135. uint256 underlyingSupplyBefore = underlying.totalSupply();
  136. uint256 otherBalanceBefore = balanceOf(other);
  137. uint256 otherUnderlyingBalanceBefore = underlying.balanceOf(other);
  138. withdrawTo@withrevert(e, receiver, amount);
  139. bool success = !lastReverted;
  140. // liveness
  141. assert success <=> (
  142. sender != 0 && // invalid sender
  143. receiver != currentContract && // invalid receiver
  144. receiver != 0 && // invalid receiver
  145. amount <= balanceBefore // withdraw doesn't exceed balance
  146. );
  147. // effects
  148. assert success => (
  149. to_mathint(balanceOf(sender)) == balanceBefore - amount &&
  150. to_mathint(totalSupply()) == supplyBefore - amount &&
  151. to_mathint(underlying.balanceOf(currentContract)) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) &&
  152. to_mathint(underlying.balanceOf(receiver)) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0)
  153. );
  154. // no side effect
  155. assert underlying.totalSupply() == underlyingSupplyBefore;
  156. assert balanceOf(other) != otherBalanceBefore => other == sender;
  157. assert underlying.balanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract);
  158. }
  159. /*
  160. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  161. │ Rule: recover liveness and effects │
  162. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  163. */
  164. rule recover(env e) {
  165. require nonpayable(e);
  166. address receiver;
  167. address other;
  168. // sanity
  169. require currentContract != underlying();
  170. requireInvariant totalSupplyIsSumOfBalances;
  171. requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
  172. mathint value = underlying.balanceOf(currentContract) - totalSupply();
  173. uint256 supplyBefore = totalSupply();
  174. uint256 balanceBefore = balanceOf(receiver);
  175. uint256 otherBalanceBefore = balanceOf(other);
  176. uint256 otherUnderlyingBalanceBefore = underlying.balanceOf(other);
  177. recover@withrevert(e, receiver);
  178. bool success = !lastReverted;
  179. // liveness
  180. assert success <=> receiver != 0;
  181. // effect
  182. assert success => (
  183. to_mathint(balanceOf(receiver)) == balanceBefore + value &&
  184. to_mathint(totalSupply()) == supplyBefore + value &&
  185. totalSupply() == underlying.balanceOf(currentContract)
  186. );
  187. // no side effect
  188. assert underlying.balanceOf(other) == otherUnderlyingBalanceBefore;
  189. assert balanceOf(other) != otherBalanceBefore => other == receiver;
  190. }