-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathInvariants.t.sol
133 lines (105 loc) · 5.71 KB
/
Invariants.t.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
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.7;
import { InvariantTest, TestUtils } from "../../modules/contract-test-utils/contracts/test.sol";
import { MockERC20 } from "../../modules/erc20/contracts/test/mocks/MockERC20.sol";
import { InvariantERC20TransferUser } from "./accounts/ERC20User.sol";
import { InvariantOwner } from "./accounts/Owner.sol";
import { InvariantStakerManager } from "./accounts/Staker.sol";
import { Warper } from "./accounts/Warper.sol";
import { MutableRDT } from "./utils/MutableRDT.sol";
// Invariant 1: totalAssets <= underlying balance of contract (with rounding)
// Invariant 2: ∑ balanceOfAssets == totalAssets (with rounding)
// Invariant 3: totalSupply <= totalAssets
// Invariant 4: convertToAssets(totalSupply) == totalAssets (with rounding)
// Invariant 5: exchangeRate >= `precision`
// Invariant 6: freeAssets <= totalAssets
// Invariant 7: balanceOfAssets >= balanceOf
// Invariant 8: freeAssets <= underlying balance
// Invariant 9: issuanceRate == 0 (if post vesting)
// Invariant 10: issuanceRate > 0 (if mid vesting)
contract RDTInvariants is TestUtils, InvariantTest {
InvariantERC20TransferUser internal _erc20User;
InvariantOwner internal _owner;
InvariantStakerManager internal _stakerManager;
MockERC20 internal _underlying;
MutableRDT internal _rdToken;
Warper internal _warper;
function setUp() public virtual {
_underlying = new MockERC20("MockToken", "MT", 18);
_rdToken = new MutableRDT("Revenue Distribution Token", "RDT", address(this), address(_underlying), 1e30);
_erc20User = new InvariantERC20TransferUser(address(_rdToken), address(_underlying));
_stakerManager = new InvariantStakerManager(address(_rdToken), address(_underlying));
_owner = new InvariantOwner(address(_rdToken), address(_underlying));
_warper = new Warper(address(_rdToken));
// Required to prevent `acceptOwner` from being a target function
// TODO: Investigate hevm.store error: `hevm: internal error: unexpected failure code`
_rdToken.setOwner(address(_owner));
// Performs random transfers of underlying into contract
targetContract(address(_erc20User));
// Performs random transfers of underlying into contract
// Performs random updateVestingSchedule calls
targetContract(address(_owner));
// Performs random instantiations of new staker users
// Performs random deposit calls from a random instantiated staker
// Performs random withdraw calls from a random instantiated staker
// Performs random redeem calls from a random instantiated staker
targetContract(address(_stakerManager));
// Performs random warps forward in time
targetContract(address(_warper));
// Create one staker to prevent underflow on index calculations
_stakerManager.createStaker();
_stakerManager.deposit(1, 0);
excludeContract(address(_underlying));
excludeContract(address(_rdToken));
targetSender(address(0xdeed));
}
function invariant_totalAssets_lte_underlyingBalance() public {
assertTrue(_rdToken.totalAssets() <= _underlying.balanceOf(address(_rdToken)));
}
function invariant_sumBalanceOfAssets_eq_totalAssets() public {
// Only relevant if deposits exist
if (_rdToken.totalSupply() > 0) {
uint256 sumBalanceOfAssets;
uint256 stakerCount = _stakerManager.getStakerCount();
for (uint256 i; i < stakerCount; ++i) {
sumBalanceOfAssets += _rdToken.balanceOfAssets(address(_stakerManager.stakers(i)));
}
assertTrue(sumBalanceOfAssets <= _rdToken.totalAssets());
assertWithinDiff(sumBalanceOfAssets, _rdToken.totalAssets(), stakerCount); // Rounding error of one per user
}
}
function invariant_totalSupply_lte_totalAssets() public {
assertTrue(_rdToken.totalSupply() <= _rdToken.totalAssets());
}
function invariant_totalSupply_times_exchangeRate_eq_totalAssets() public {
if (_rdToken.totalSupply() > 0) {
assertWithinDiff(_rdToken.convertToAssets(_rdToken.totalSupply()), _rdToken.totalAssets(), 1); // One division
}
}
// TODO: figure out if there's a replacement for this one involving convertTo* functions. I think Invariant 3: totalSupply <= totalAssets covers this.
// function invariant_exchangeRate_gte_precision() public {
// assertTrue(_rdToken.exchangeRate() >= _rdToken.precision());
// }
function invariant_freeAssets_lte_totalAssets() public {
assertTrue(_rdToken.freeAssets() <= _rdToken.totalAssets());
}
function invariant_balanceOfAssets_gte_balanceOf() public {
for (uint256 i; i < _stakerManager.getStakerCount(); ++i) {
address staker = address(_stakerManager.stakers(i));
assertTrue(_rdToken.balanceOfAssets(staker) >= _rdToken.balanceOf(staker));
}
}
function invariant_freeAssets_lte_underlyingBalance() public {
assertTrue(_rdToken.freeAssets() <= _underlying.balanceOf(address(_rdToken)));
}
function invariant_issuanceRate_eq_zero_ifPostVesting() public {
if (block.timestamp > _rdToken.vestingPeriodFinish() && _rdToken.lastUpdated() > _rdToken.vestingPeriodFinish()) {
assertTrue(_rdToken.issuanceRate() == 0);
}
}
function invariant_issuanceRate_gt_zero_ifMidVesting() public {
if (block.timestamp <= _rdToken.vestingPeriodFinish()) {
assertTrue(_rdToken.issuanceRate() > 0);
}
}
}