forked from crytic/properties
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathERC4626PropertyTestBase.sol
180 lines (155 loc) · 7.88 KB
/
ERC4626PropertyTestBase.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.0;
import {IERC20} from "../../util/IERC20.sol";
import {IERC4626} from "../../util/IERC4626.sol";
import {TestERC20Token} from "./TestERC20Token.sol";
import {Actor} from "../util/Actor.sol";
import {PropertiesAsserts} from "../../util/PropertiesHelper.sol";
import {RedemptionProxy} from "./RedemptionProxy.sol";
/// @notice This contract is used as a base contract for all 4626 property tests.
contract CryticERC4626PropertyBase is PropertiesAsserts {
TestERC20Token asset;
IERC4626 vault;
Actor alice;
Actor bob; //remove?
RedemptionProxy redemptionProxy;
// feature flags
bool supportsInternalTestingIface;
function initialize(
address _vault,
address _asset,
bool _supportsInternalTestingIface
) internal {
vault = IERC4626(_vault);
asset = TestERC20Token(_asset);
alice = new Actor(vault);
bob = new Actor(vault);
redemptionProxy = new RedemptionProxy(vault);
supportsInternalTestingIface = _supportsInternalTestingIface;
}
/// @notice Funds the `owner` address with `tokens` & forces a token approval for the vault to spend owner's tokens.
function prepareAddressForDeposit(address owner, uint256 tokens) internal {
asset.mint(owner, tokens);
asset.forceApproval(owner, address(vault), tokens);
}
/// @notice Measures the `target`'s assets and shares, and emits events to assist in debugging property failures.
/// @param target A address to target
/// @param name A name for the target address (alice, bob, vault, etc.)
/// @param annotation An additional piece of metadata for debugging ie: "before deposit", "after mint", etc.
function measureAddressHoldings(
address target,
string memory name,
string memory annotation
) internal returns (uint256 assetBalance, uint256 shareBalance) {
assetBalance = asset.balanceOf(target);
shareBalance = vault.balanceOf(target);
string memory assetMsg = string(
abi.encodePacked("asset.balanceOf(", name, ") (", annotation, ")")
);
emit LogUint256(assetMsg, assetBalance);
string memory shareMsg = string(
abi.encodePacked("vault.balanceOf(", name, ") (", annotation, ")")
);
emit LogUint256(shareMsg, shareBalance);
}
/// @notice Prevents `party` from resolving to addresses which have special accounting rules.
function restrictAddressToThirdParties(
uint256 partyIndex
) internal view returns (address) {
// set up 3 static third parties
partyIndex = partyIndex % 3;
if (partyIndex == 0) {
return address(this);
}
if (partyIndex == 1) {
return 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa;
}
return 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;
}
/// @notice Performs all the checks required to ensure a successful vault deposit. This includes funding the owner account and clamping token amounts as needed.
/// It is assumed that successful calls to requireValidDepositAmount imply that vault.deposit() will not revert. This implied property might not hold for certain
/// vault implementations, and should be modified if exceptions are discovered.
function requireValidDepositAmount(
address owner,
address receiver,
uint256 tokens
) internal returns (uint256) {
tokens = clampLte(tokens, vault.maxDeposit(receiver));
tokens = clampGt(tokens, 0);
prepareAddressForDeposit(owner, tokens);
// The following logic is intended to revert when an unreasonably large deposit is being made.
uint256 sharesMinted = vault.convertToShares(tokens);
uint256 currentShares = vault.balanceOf(receiver);
vault.convertToAssets(sharesMinted + currentShares);
//uint256 sharesRedeemed = vault.previewWithdraw(tokensWithdrawn);
emit LogUint256("Tokens to use in deposit:", tokens);
// configure with setting?
require(vault.previewDeposit(tokens) > 0);
return tokens;
}
/// @notice Performs all the checks required to ensure a successful vault mint. This includes funding the owner account and clamping token amounts as needed.
/// It is assumed that successful calls to requireValidDepositAmount imply that vault.mint() will not revert. This implied property might not hold for certain
/// vault implementations, and should be modified if exceptions are discovered.
function requireValidMintAmount(
address owner,
address receiver,
uint256 shares
) internal returns (uint256) {
shares = clampLte(shares, vault.maxMint(receiver));
uint256 tokensDeposited = vault.previewMint(shares);
prepareAddressForDeposit(owner, tokensDeposited);
// The following logic is intended to revert when an unreasonably large mint is being made.
uint256 currentShares = vault.balanceOf(receiver);
vault.previewRedeem(currentShares + shares);
emit LogUint256("Shares to use in mint:", shares);
// configure with setting?
// require(vault.previewMint(shares) > 0);
return shares;
}
/// @notice Performs all the checks required to ensure a successful vault redeem. This includes funding the owner account and clamping token amounts as needed.
/// It is assumed that successful calls to requireValidDepositAmount imply that vault.redeem() will not revert. This implied property might not hold for certain
/// vault implementations, and should be modified if exceptions are discovered.
function requireValidRedeemAmount(
address owner,
uint256 shares
) internal returns (uint256) {
// should this be a configured setting?
require(shares > 0);
uint256 maxRedeem = vault.maxRedeem(owner);
require(maxRedeem > 0);
uint256 ownerShares = vault.balanceOf(owner);
require(ownerShares > 0);
shares = clampLte(shares, maxRedeem);
shares = clampLte(shares, ownerShares);
// The following logic is intended to revert when an unreasonably large redemption is being made.
uint256 tokensWithdrawn = vault.convertToAssets(shares);
vault.previewRedeem(shares);
// should this be a configured setting?
require(tokensWithdrawn > 0);
emit LogUint256("Shares to use in redemption:", shares);
return shares;
}
/// @notice Performs all the checks required to ensure a successful vault withdraw. This includes funding the owner account and clamping token amounts as needed.
/// It is assumed that successful calls to requireValidDepositAmount imply that vault.withdraw() will not revert. This implied property might not hold for certain
/// vault implementations, and should be modified if exceptions are discovered.
function requireValidWithdrawAmount(
address owner,
uint256 tokens
) internal returns (uint256) {
uint256 maxWithdraw = vault.maxWithdraw(owner);
require(maxWithdraw > 0);
uint256 ownerBalance = vault.balanceOf(owner);
require(ownerBalance > 0);
uint256 sharesToRedeem = vault.previewWithdraw(tokens);
sharesToRedeem = clampLte(sharesToRedeem, vault.balanceOf(owner));
require(sharesToRedeem <= ownerBalance);
// not easy to clamp these without making this code a lot more complex.
uint256 clampedTokens = vault.previewRedeem(sharesToRedeem);
require(clampedTokens <= maxWithdraw);
// should this be a configured setting?
require(sharesToRedeem > 0);
// we don't need to check for unreasonably large withdraws because previewWithdraw would have reverted.
emit LogUint256("Tokens to use in withdraw:", clampedTokens);
return clampedTokens;
}
}