-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Add FV specification for ERC20Wrapper #4100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
099bf14
03c35b0
2a74f88
4a9b4d2
056cc3f
55aa62a
9f73887
ebf8df6
3e14352
1b621ad
df17e38
8c7ce2b
fbdbddd
3800164
0c57f1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'openzeppelin-solidity': minor | ||
| --- | ||
|
|
||
| `ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitelly forbiden. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.0; | ||
|
|
||
| import "../patched/token/ERC20/extensions/ERC20Wrapper.sol"; | ||
|
|
||
| contract ERC20WrapperHarness is ERC20Wrapper { | ||
| constructor(IERC20 _underlying, string memory _name, string memory _symbol) ERC20(_name, _symbol) ERC20Wrapper(_underlying) {} | ||
|
|
||
| function underlyingTotalSupply() public view returns (uint256) { | ||
| return underlying().totalSupply(); | ||
| } | ||
|
|
||
| function underlyingBalanceOf(address account) public view returns (uint256) { | ||
| return underlying().balanceOf(account); | ||
| } | ||
|
|
||
| function underlyingAllowanceToThis(address account) public view returns (uint256) { | ||
| return underlying().allowance(account, address(this)); | ||
| } | ||
|
|
||
| function recover(address account) public returns (uint256) { | ||
| return _recover(account); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| import "helpers.spec" | ||
| import "ERC20.spec" | ||
|
|
||
| methods { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we specify an I just noticed this in the docs and I find it concerning!
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK:
|
||
| underlying() returns(address) envfree | ||
| underlyingTotalSupply() returns(uint256) envfree | ||
| underlyingBalanceOf(address) returns(uint256) envfree | ||
| underlyingAllowanceToThis(address) returns(uint256) envfree | ||
|
|
||
| depositFor(address, uint256) returns(bool) | ||
| withdrawTo(address, uint256) returns(bool) | ||
| recover(address) returns(uint256) | ||
| } | ||
|
|
||
| use invariant totalSupplyIsSumOfBalances | ||
|
|
||
| /* | ||
| ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | ||
| │ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying │ | ||
| └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | ||
| */ | ||
| function underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool { | ||
| return underlyingBalanceOf(a) <= underlyingTotalSupply(); | ||
| } | ||
|
|
||
| function sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool { | ||
| return a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= underlyingTotalSupply(); | ||
| } | ||
|
|
||
| /* | ||
| ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | ||
| │ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) │ | ||
| └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | ||
| */ | ||
| invariant totalSupplyIsSmallerThanUnderlyingBalance() | ||
| totalSupply() <= underlyingBalanceOf(currentContract) && | ||
| underlyingBalanceOf(currentContract) <= underlyingTotalSupply() && | ||
| underlyingTotalSupply() <= max_uint256 | ||
| { | ||
| preserved { | ||
| requireInvariant totalSupplyIsSumOfBalances; | ||
| require underlyingBalancesLowerThanUnderlyingSupply(currentContract); | ||
| } | ||
| preserved depositFor(address account, uint256 amount) with (env e) { | ||
| require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract); | ||
| } | ||
| } | ||
|
|
||
| invariant noSelfWrap() | ||
| currentContract != underlying() | ||
|
|
||
| /* | ||
| ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | ||
| │ Rule: depositFor liveness and effects │ | ||
| └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | ||
| */ | ||
| rule depositFor(env e) { | ||
| require nonpayable(e); | ||
|
|
||
| address sender = e.msg.sender; | ||
| address receiver; | ||
| address other; | ||
| uint256 amount; | ||
|
|
||
| // sanity | ||
| requireInvariant noSelfWrap; | ||
| requireInvariant totalSupplyIsSumOfBalances; | ||
| requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; | ||
| require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender); | ||
|
|
||
| uint256 balanceBefore = balanceOf(receiver); | ||
| uint256 supplyBefore = totalSupply(); | ||
| uint256 senderUnderlyingBalanceBefore = underlyingBalanceOf(sender); | ||
| uint256 senderUnderlyingAllowanceBefore = underlyingAllowanceToThis(sender); | ||
| uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract); | ||
| uint256 underlyingSupplyBefore = underlyingTotalSupply(); | ||
|
|
||
| uint256 otherBalanceBefore = balanceOf(other); | ||
| uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); | ||
|
|
||
| depositFor@withrevert(e, receiver, amount); | ||
| bool success = !lastReverted; | ||
|
|
||
| // liveness | ||
| assert success <=> ( | ||
| sender != currentContract && // invalid sender | ||
| sender != 0 && // invalid sender | ||
| receiver != 0 && // invalid receiver | ||
| amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance | ||
| amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance | ||
| ); | ||
|
|
||
| // effects | ||
| assert success => ( | ||
| balanceOf(receiver) == balanceBefore + amount && | ||
| totalSupply() == supplyBefore + amount && | ||
| underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore + amount && | ||
| underlyingBalanceOf(sender) == senderUnderlyingBalanceBefore - amount | ||
| ); | ||
|
|
||
| // no side effect | ||
| assert underlyingTotalSupply() == underlyingSupplyBefore; | ||
| assert balanceOf(other) != otherBalanceBefore => other == receiver; | ||
| assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract); | ||
| } | ||
|
|
||
| /* | ||
| ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | ||
| │ Rule: withdrawTo liveness and effects │ | ||
| └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | ||
| */ | ||
| rule withdrawTo(env e) { | ||
| require nonpayable(e); | ||
|
|
||
| address sender = e.msg.sender; | ||
| address receiver; | ||
| address other; | ||
| uint256 amount; | ||
|
|
||
| // sanity | ||
| requireInvariant noSelfWrap; | ||
| requireInvariant totalSupplyIsSumOfBalances; | ||
| requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; | ||
| require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver); | ||
|
|
||
| uint256 balanceBefore = balanceOf(sender); | ||
| uint256 supplyBefore = totalSupply(); | ||
| uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver); | ||
| uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract); | ||
| uint256 underlyingSupplyBefore = underlyingTotalSupply(); | ||
|
|
||
| uint256 otherBalanceBefore = balanceOf(other); | ||
| uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); | ||
|
|
||
| withdrawTo@withrevert(e, receiver, amount); | ||
| bool success = !lastReverted; | ||
|
|
||
| // liveness | ||
| assert success <=> ( | ||
| sender != 0 && // invalid sender | ||
| receiver != 0 && // invalid receiver | ||
| amount <= balanceBefore // withdraw doesn't exceed balance | ||
| ); | ||
|
|
||
| // effects | ||
| assert success => ( | ||
| balanceOf(sender) == balanceBefore - amount && | ||
| totalSupply() == supplyBefore - amount && | ||
| underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) && | ||
| underlyingBalanceOf(receiver) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0) | ||
| ); | ||
|
|
||
| // no side effect | ||
| assert underlyingTotalSupply() == underlyingSupplyBefore; | ||
| assert balanceOf(other) != otherBalanceBefore => other == sender; | ||
| assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract); | ||
| } | ||
|
|
||
| /* | ||
| ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | ||
| │ Rule: recover liveness and effects │ | ||
| └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | ||
| */ | ||
| rule recover(env e) { | ||
| require nonpayable(e); | ||
|
|
||
| address receiver; | ||
| address other; | ||
|
|
||
| // sanity | ||
| requireInvariant noSelfWrap; | ||
| requireInvariant totalSupplyIsSumOfBalances; | ||
| requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; | ||
|
|
||
| uint256 value = underlyingBalanceOf(currentContract) - totalSupply(); | ||
| uint256 supplyBefore = totalSupply(); | ||
| uint256 balanceBefore = balanceOf(receiver); | ||
|
|
||
| uint256 otherBalanceBefore = balanceOf(other); | ||
| uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); | ||
|
|
||
| recover@withrevert(e, receiver); | ||
| bool success = !lastReverted; | ||
|
|
||
| // liveness | ||
| assert success <=> receiver != 0; | ||
|
|
||
| // effect | ||
| assert success => ( | ||
| balanceOf(receiver) == balanceBefore + value && | ||
| totalSupply() == supplyBefore + value && | ||
| totalSupply() == underlyingBalanceOf(currentContract) | ||
| ); | ||
|
|
||
| // no side effect | ||
| assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore; | ||
| assert balanceOf(other) != otherBalanceBefore => other == receiver; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.