-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathDispenser.t.sol
More file actions
370 lines (313 loc) · 17.4 KB
/
Copy pathDispenser.t.sol
File metadata and controls
370 lines (313 loc) · 17.4 KB
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
pragma solidity ^0.8.30;
import {Test} from "forge-std/Test.sol";
import {Utils} from "./utils/Utils.sol";
import {Dispenser} from "../contracts/Dispenser.sol";
import {DispenserProxy} from "../contracts/proxies/DispenserProxy.sol";
import "../contracts/Tokenomics.sol";
import {TokenomicsProxy} from "../contracts/proxies/TokenomicsProxy.sol";
import {Treasury} from "../contracts/Treasury.sol";
import {ERC20Token} from "../contracts/test/ERC20Token.sol";
import {MockRegistry} from "../contracts/test/MockRegistry.sol";
import {MockVE} from "../contracts/test/MockVE.sol";
contract BaseSetup is Test {
Utils internal utils;
Dispenser internal dispenser;
ERC20Token internal olas;
MockRegistry internal componentRegistry;
MockRegistry internal agentRegistry;
MockRegistry internal serviceRegistry;
MockVE internal ve;
Treasury internal treasury;
Tokenomics internal tokenomics;
uint256[] internal emptyArray;
uint256[] internal serviceIds;
uint256[] internal serviceAmounts;
uint256[] internal unitTypes;
uint256[] internal unitIds;
address payable[] internal users;
address internal deployer;
bytes32 internal retainer;
address internal dev;
uint256 internal initialMint = 10_000_000_000e18;
uint256 internal largeApproval = 1_000_000_000_000e18;
uint256 internal epochLen = 30 days;
uint256 internal snapshot = vm.snapshotState();
function setUp() public virtual {
emptyArray = new uint256[](0);
serviceIds = new uint256[](2);
(serviceIds[0], serviceIds[1]) = (1, 2);
serviceAmounts = new uint256[](2);
unitTypes = new uint256[](2);
unitIds = new uint256[](2);
utils = new Utils();
users = utils.createUsers(2);
deployer = users[0];
vm.label(deployer, "Deployer");
dev = users[1];
vm.label(dev, "Developer");
retainer = bytes32(uint256(uint160(deployer)));
// Deploy contracts
olas = new ERC20Token();
ve = new MockVE();
componentRegistry = new MockRegistry();
agentRegistry = new MockRegistry();
serviceRegistry = new MockRegistry();
// Depository and dispenser contracts are irrelevant at this point, so we are using a deployer's address
// Correct tokenomics and dispenser addresses will be added below
treasury = new Treasury(address(olas), deployer, deployer, deployer);
Tokenomics tokenomicsMaster = new Tokenomics();
// Depository contract is irrelevant here, so we are using a deployer's address
// Dispenser address is a placeholder until the dispenser proxy is deployed below
bytes memory proxyData = abi.encodeWithSelector(tokenomicsMaster.initializeTokenomics.selector,
address(olas), address(treasury), deployer, deployer, address(ve), epochLen,
address(componentRegistry), address(agentRegistry), address(serviceRegistry), address(0));
TokenomicsProxy tokenomicsProxy = new TokenomicsProxy(address(tokenomicsMaster), proxyData);
tokenomics = Tokenomics(address(tokenomicsProxy));
// Deploy dispenser implementation (tokenomics proxy address is an implementation immutable) and proxy
// Vote Weighting contract is irrelevant here, so we are using a deployer's address
Dispenser dispenserMaster = new Dispenser(address(olas), address(tokenomics), retainer, 100, 1 ether);
bytes memory dispenserData = abi.encodeWithSelector(dispenserMaster.initialize.selector,
address(treasury), deployer, 100, 100);
DispenserProxy dispenserProxy = new DispenserProxy(address(dispenserMaster), dispenserData);
dispenser = Dispenser(address(dispenserProxy));
// Change tokenomics and dispenser addresses in treasury
treasury.changeManagers(address(tokenomics), address(0), address(dispenser));
// Change the dispenser address in tokenomics to the correct one
tokenomics.changeManagers(address(0), address(0), address(dispenser));
// Set treasury contract as a minter for OLAS
olas.changeMinter(address(treasury));
}
}
contract DispenserTest is BaseSetup {
function setUp() public override {
super.setUp();
}
/// @dev Pseudo-random number generator in a range of 0 to 1000.
function random(uint256 seed) private pure returns (uint256) {
uint256 randomHash = uint256(keccak256(abi.encodePacked(seed)));
return randomHash % 1000;
}
/// @dev Deposit incentives for 2 services.
/// @notice Assume that no single donation is bigger than 2^64 - 1.
/// @param amount0 Amount to donate to the first service.
/// @param amount1 Amount to donate to the second service.
function testIncentives(uint64 amount0, uint64 amount1) public {
// Amounts must be meaningful
vm.assume(amount0 > treasury.minAcceptedETH());
vm.assume(amount1 > treasury.minAcceptedETH());
// Try to claim empty incentives
vm.prank(deployer);
vm.expectRevert();
(uint256 reward, uint256 topUp) = dispenser.claimOwnerIncentives(emptyArray, emptyArray);
assertEq(reward, 0);
assertEq(topUp, 0);
// Lock OLAS balances with Voting Escrow
ve.setWeightedBalance(tokenomics.veOLASThreshold());
ve.createLock(deployer);
// Change the first service owner to the deployer (same for components and agents)
serviceRegistry.changeUnitOwner(1, deployer);
componentRegistry.changeUnitOwner(1, deployer);
agentRegistry.changeUnitOwner(1, deployer);
// Send donations to services from the deployer
(serviceAmounts[0], serviceAmounts[1]) = (amount0, amount1);
vm.prank(deployer);
treasury.depositServiceDonationsETH{value: serviceAmounts[0] + serviceAmounts[1]}(serviceIds, serviceAmounts);
// Move at least epochLen seconds in time
vm.warp(block.timestamp + epochLen + 10);
// Mine a next block to avoid a flash loan attack condition
vm.roll(block.number + 1);
// Start new epoch and calculate tokenomics parameters and rewards
tokenomics.checkpoint();
// Get the last settled epoch counter
uint256 lastPoint = tokenomics.epochCounter() - 1;
assertEq(lastPoint, 1);
// Get the epoch point of the last epoch
EpochPoint memory ep = tokenomics.mapEpochTokenomics(lastPoint);
// Get the unit points of the last epoch
UnitPoint memory up0 = tokenomics.getUnitPoint(lastPoint, 0);
UnitPoint memory up1 = tokenomics.getUnitPoint(lastPoint, 1);
// Calculate rewards based on the points information
uint256 rewards0 = (ep.totalDonationsETH * up0.rewardUnitFraction) / 100;
uint256 rewards1 = (ep.totalDonationsETH * up1.rewardUnitFraction) / 100;
uint256 accountRewards = rewards0 + rewards1;
// Calculate top-ups based on the points information
uint256 topUps0 = (ep.totalTopUpsOLAS * up0.topUpUnitFraction) / 100;
uint256 topUps1 = (ep.totalTopUpsOLAS * up1.topUpUnitFraction) / 100;
uint256 accountTopUps = topUps0 + topUps1;
// Rewards and top-ups must not be zero
assertGt(accountRewards, 0);
assertGt(accountTopUps, 0);
// Check for the incentive balances of component and agent such that their pending relative incentives are non-zero
(, uint256 pendingRelativeReward, , uint256 pendingRelativeTopUp, ) = tokenomics.mapUnitIncentives(0, 1);
assertGt(pendingRelativeReward, 0);
assertGt(pendingRelativeTopUp, 0);
(, pendingRelativeReward, , pendingRelativeTopUp, ) = tokenomics.mapUnitIncentives(1, 1);
assertGt(pendingRelativeReward, 0);
assertGt(pendingRelativeTopUp, 0);
// Define the types of units to claim rewards and top-ups for
(unitTypes[0], unitTypes[1]) = (0, 1);
// Define unit Ids to claim rewards and top-ups for
(unitIds[0], unitIds[1]) = (1, 1);
// Claim rewards and top-ups
uint256 balanceETH = address(deployer).balance;
uint256 balanceOLAS = olas.balanceOf(deployer);
vm.prank(deployer);
(rewards0, topUps0) = dispenser.claimOwnerIncentives(unitTypes, unitIds);
// Check the ETH and OLAS balance after receiving incentives
balanceETH = address(deployer).balance - balanceETH;
balanceOLAS = olas.balanceOf(deployer) - balanceOLAS;
assertEq(balanceETH, accountRewards);
assertEq(balanceOLAS, accountTopUps);
vm.revertToState(snapshot);
}
/// @dev Deposit incentives for 2 services in a loop to go through a specified amount of time.
/// @notice Assume that no single donation is bigger than 2^64 - 1.
/// @param amount0 Amount to donate to the first service.
/// @param amount1 Amount to donate to the second service.
function testIncentivesLoopDirect(uint64 amount0, uint64 amount1) public {
// Amounts must be meaningful
vm.assume(amount0 > treasury.minAcceptedETH());
vm.assume(amount1 > treasury.minAcceptedETH());
// Lock OLAS balances with Voting Escrow
ve.setWeightedBalance(tokenomics.veOLASThreshold());
ve.createLock(deployer);
// Change the first service owner to the deployer (same for components and agents)
serviceRegistry.changeUnitOwner(1, deployer);
componentRegistry.changeUnitOwner(1, deployer);
agentRegistry.changeUnitOwner(1, deployer);
// Define the types of units to claim rewards and top-ups for
(unitTypes[0], unitTypes[1]) = (0, 1);
// Define unit Ids to claim rewards and top-ups for
(unitIds[0], unitIds[1]) = (1, 1);
// Run for more than 2 years (more than 52 weeks in a year)
uint256 endTime = 250 weeks;
uint256 lastPoint = tokenomics.epochCounter() - 1;
uint256 effectiveBond = tokenomics.effectiveBond();
for (uint256 i = 0; i < endTime; i += epochLen) {
// Send donations to services from the deployer
(serviceAmounts[0], serviceAmounts[1]) = (amount0, amount1);
vm.prank(deployer);
treasury.depositServiceDonationsETH{value: serviceAmounts[0] + serviceAmounts[1]}(serviceIds, serviceAmounts);
// Get the current year number
uint256 curYear = tokenomics.currentYear();
// Move at least epochLen seconds in time
vm.warp(block.timestamp + epochLen);
// Mine a next block to avoid a flash loan attack condition
vm.roll(block.number + i + 1);
// Check that the epoch counter is changed from the last time
assertEq(lastPoint, tokenomics.epochCounter() - 1);
// Start new epoch and calculate tokenomics parameters and rewards
tokenomics.checkpoint();
// Get the last settled epoch counter
lastPoint = tokenomics.epochCounter() - 1;
// Get the epoch point of the last epoch
EpochPoint memory ep = tokenomics.mapEpochTokenomics(lastPoint);
// Get the unit points of the last epoch
UnitPoint[] memory up = new UnitPoint[](2);
(up[0], up[1]) = (tokenomics.getUnitPoint(lastPoint, 0), tokenomics.getUnitPoint(lastPoint, 1));
// Calculate rewards based on the points information
uint256 rewards0 = (ep.totalDonationsETH * up[0].rewardUnitFraction) / 100;
uint256 rewards1 = (ep.totalDonationsETH * up[1].rewardUnitFraction) / 100;
uint256 accountRewards = rewards0 + rewards1;
// Calculate top-ups based on the points information
uint256 topUps0 = (ep.totalTopUpsOLAS * up[0].topUpUnitFraction) / 100;
uint256 topUps1 = (ep.totalTopUpsOLAS * up[1].topUpUnitFraction) / 100;
uint256 accountTopUps = topUps0 + topUps1;
// Calculate maxBond
uint256 calculatedMaxBond = (ep.totalTopUpsOLAS * ep.maxBondFraction) / 100;
// Compare it with the max bond calculated from the fraction and the total OLAS inflation for the epoch
// Do not compare directly if the next epoch is the epoch where the year changes
uint256 numYearsNextEpoch = (block.timestamp + tokenomics.epochLen() - tokenomics.timeLaunch()) / tokenomics.ONE_YEAR();
if (numYearsNextEpoch == curYear) {
assertEq(tokenomics.maxBond(), calculatedMaxBond);
}
// Effective bond must be the previous effective bond plus the actual maxBond
assertEq(effectiveBond + tokenomics.maxBond(), tokenomics.effectiveBond());
effectiveBond += tokenomics.maxBond();
// Rewards and top-ups must not be zero
assertGt(accountRewards, 0);
assertGt(accountTopUps, 0);
// Claim rewards and top-ups
uint256 balanceETH = address(deployer).balance;
uint256 balanceOLAS = olas.balanceOf(deployer);
vm.prank(deployer);
(rewards0, topUps0) = dispenser.claimOwnerIncentives(unitTypes, unitIds);
// Check the ETH and OLAS balance after receiving incentives
balanceETH = address(deployer).balance - balanceETH;
balanceOLAS = olas.balanceOf(deployer) - balanceOLAS;
assertEq(balanceETH, accountRewards);
assertEq(balanceOLAS, accountTopUps);
}
vm.revertToState(snapshot);
}
/// @dev Deposit incentives in a loop as the previous one and claim incentives ones in two epochs.
/// @notice Assume that no single donation is bigger than 2^64 - 1.
/// @param amount0 Amount to donate to the first service.
/// @param amount1 Amount to donate to the second service.
function testIncentivesLoopEvenOdd(uint64 amount0, uint64 amount1) public {
// Amounts must be meaningful
vm.assume(amount0 > treasury.minAcceptedETH());
vm.assume(amount1 > treasury.minAcceptedETH());
// Lock OLAS balances with Voting Escrow
ve.setWeightedBalance(tokenomics.veOLASThreshold());
ve.createLock(deployer);
// Change the first service owner to the deployer (same for components and agents)
serviceRegistry.changeUnitOwner(1, deployer);
componentRegistry.changeUnitOwner(1, deployer);
agentRegistry.changeUnitOwner(1, deployer);
// Define the types of units to claim rewards and top-ups for
(unitTypes[0], unitTypes[1]) = (0, 1);
// Define unit Ids to claim rewards and top-ups for
(unitIds[0], unitIds[1]) = (1, 1);
uint256[] memory rewards = new uint256[](2);
uint256[] memory topUps = new uint256[](2);
// Run for more than 2 years (more than 52 weeks in a year)
uint256 endTime = 110 weeks;
for (uint256 i = 0; i < endTime; i += epochLen) {
// Send donations to services from the deployer
(serviceAmounts[0], serviceAmounts[1]) = (amount0, amount1);
vm.prank(deployer);
treasury.depositServiceDonationsETH{value: serviceAmounts[0] + serviceAmounts[1]}(serviceIds, serviceAmounts);
// Move at least epochLen seconds in time with the random addition of seconds
vm.warp(block.timestamp + epochLen + random(tokenomics.epochCounter()));
// Mine a next block to avoid a flash loan attack condition
vm.roll(block.number + i + 1);
// Start new epoch and calculate tokenomics parameters and rewards
tokenomics.checkpoint();
// Get the last settled epoch counter
uint256 lastPoint = tokenomics.epochCounter() - 1;
// Get the epoch point of the last epoch
EpochPoint memory ep = tokenomics.mapEpochTokenomics(lastPoint);
// Get the unit points of the last epoch
UnitPoint[] memory up = new UnitPoint[](2);
(up[0], up[1]) = (tokenomics.getUnitPoint(lastPoint, 0), tokenomics.getUnitPoint(lastPoint, 1));
// Calculate rewards based on the points information
rewards[0] += (ep.totalDonationsETH * up[0].rewardUnitFraction) / 100;
rewards[1] += (ep.totalDonationsETH * up[1].rewardUnitFraction) / 100;
// Calculate top-ups based on the points information
topUps[0] += (ep.totalTopUpsOLAS * up[0].topUpUnitFraction) / 100;
topUps[1] += (ep.totalTopUpsOLAS * up[1].topUpUnitFraction) / 100;
// Claim rewards and top-ups during even epoch numbers
if ((tokenomics.epochCounter() % 2) == 0) {
// This will be a sum up of two epoch incentives
uint256 accountRewards = rewards[0] + rewards[1];
uint256 accountTopUps = topUps[0] + topUps[1];
uint256 balanceETH = address(deployer).balance;
uint256 balanceOLAS = olas.balanceOf(deployer);
vm.prank(deployer);
(rewards[0], topUps[0]) = dispenser.claimOwnerIncentives(unitTypes, unitIds);
// Check the ETH and OLAS balance after receiving incentives
balanceETH = address(deployer).balance - balanceETH;
balanceOLAS = olas.balanceOf(deployer) - balanceOLAS;
assertEq(balanceETH, accountRewards);
assertEq(balanceOLAS, accountTopUps);
// Zero previously calculated rewards and top-ups
rewards[0] = 0;
rewards[1] = 0;
topUps[0] = 0;
topUps[1] = 0;
}
}
vm.revertToState(snapshot);
}
}