Skip to content

Commit 466ca3c

Browse files
committed
🚧 setup testing
1 parent 52deba5 commit 466ca3c

File tree

11 files changed

+267
-14
lines changed

11 files changed

+267
-14
lines changed

src/lockers/AaveLocker.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ contract AaveV3Locker is AbstractLocker {
7474
// Calculate current value of position in underlying token.
7575
uint256 withdrawableBalance = getTotalValue(asset);
7676

77-
// The yield is the difference between current claimable balance and total deposited.
7877
// Cache value
7978
uint256 totalDeposited_ = totalDeposited;
79+
totalDeposited = 0;
80+
81+
// The yield is the difference between current claimable balance and total deposited.
8082
yield = withdrawableBalance > totalDeposited_ ? withdrawableBalance - totalDeposited_ : 0;
8183

8284
uint256 totalWithdrawn = AAVE_POOL.withdraw(asset, type(uint256).max, msg.sender);

src/token/EurB.sol

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {IERC20} from "../../lib/openzeppelin-contracts/contracts/interfaces/IERC
1010
import {ISafe} from "./interfaces/ISafe.sol";
1111
import {ILocker} from "../lockers/interfaces/ILocker.sol";
1212
import {Ownable} from "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol";
13+
import {ReentrancyGuard} from "../../lib/solmate/src/utils/ReentrancyGuard.sol";
1314
import {SafeERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
1415
import {Storage} from "./Storage.sol";
1516

@@ -22,6 +23,7 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
2223
////////////////////////////////////////////////////////////// */
2324

2425
error IsNotALocker();
26+
error IsActiveLocker();
2527
error LengthMismatch();
2628
error LockerNotPrivate();
2729
error MaxCommissionsDepth();
@@ -40,6 +42,16 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
4042
MODIFIERS
4143
////////////////////////////////////////////////////////////// */
4244

45+
modifier nonReentrant() {
46+
require(locked == 1, "REENTRANCY");
47+
48+
locked = 2;
49+
50+
_;
51+
52+
locked = 1;
53+
}
54+
4355
/* //////////////////////////////////////////////////////////////
4456
CONSTRUCTOR
4557
////////////////////////////////////////////////////////////// */
@@ -115,8 +127,12 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
115127
YIELD LOCKERS LOGIC
116128
////////////////////////////////////////////////////////////// */
117129

118-
function syncAll() external {
130+
/**
131+
* @notice Synchronizes all yield lockers by adjusting balances based on weights and idle ratio.
132+
*/
133+
function syncAll() external nonReentrant {
119134
if (block.timestamp < lastSyncTime + 1 days) revert SyncIntervalNotMet();
135+
lastSyncTime = block.timestamp;
120136

121137
// Cache values.
122138
uint256 BIPS_ = BIPS;
@@ -154,7 +170,6 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
154170
}
155171

156172
// Get total amount that should be deposited in lockers (non-idle).
157-
// Note : check if ok to keep same totalSupply here (think should be ok)
158173
uint256 totalToInvest = totalSupplyExclPrivate.mulDivDown(BIPS_ - idleRatio, BIPS_);
159174

160175
// We use weights.length as those should always sum to BIPS (see setWeights()).
@@ -164,7 +179,6 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
164179
uint256 currentBalance = ILocker(lockers[i]).totalDeposited();
165180

166181
if (currentBalance < targetBalance) {
167-
// Note : use batchApprove.
168182
uint256 toDeposit = targetBalance - currentBalance;
169183
IERC20(underlying_).approve(lockers[i], toDeposit);
170184
// Don't revert if the call fails, continue.
@@ -180,7 +194,10 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
180194
}
181195
}
182196

183-
// Note : It should mint the yield in underlying token and distribute to treasury
197+
/**
198+
* @notice Collects yield from all yield lockers and mints it to the treasury.
199+
* @return yield The total yield collected.
200+
*/
184201
function collectYield() external returns (uint256 yield) {
185202
if (block.timestamp - lastYieldClaim < yieldInterval) revert YieldIntervalNotMet();
186203

@@ -209,11 +226,19 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
209226
treasury = treasury_;
210227
}
211228

229+
/**
230+
* @notice Adds a new yield locker to the system.
231+
* @param locker The address of the locker to add.
232+
*/
212233
function addYieldLocker(address locker) external onlyOwner {
213234
if (yieldLockers.length == MAX_YIELD_LOCKERS) revert MaxYieldLockers();
214235
yieldLockers.push(locker);
215236
}
216237

238+
/**
239+
* @notice Removes a yield locker from the system, withdrawing its balance.
240+
* @param locker The address of the locker to remove.
241+
*/
217242
function removeYieldLocker(address locker) external onlyOwner {
218243
// Cache values
219244
address[] memory yieldLockers_ = yieldLockers;
@@ -248,6 +273,10 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
248273
lockersWeights.pop();
249274
}
250275

276+
/**
277+
* @notice Sets the weights for yield lockers.
278+
* @param newLockersWeights The new weights for each locker.
279+
*/
251280
// Note : Double check no issue if idle set to max vs lockers
252281
function setWeights(uint256[] memory newLockersWeights) external onlyOwner {
253282
if (newLockersWeights.length != yieldLockers.length) revert LengthMismatch();
@@ -259,11 +288,19 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
259288
lockersWeights = newLockersWeights;
260289
}
261290

291+
/**
292+
* @notice Sets a new idle ratio for the system.
293+
* @param newRatio The new idle ratio.
294+
*/
262295
function setIdleRatio(uint256 newRatio) external onlyOwner {
263296
if (newRatio > BIPS) revert MaxRatio();
264297
idleRatio = newRatio;
265298
}
266299

300+
/**
301+
* @notice Sets the interval for yield collection.
302+
* @param yieldInterval_ The new yield interval in seconds.
303+
*/
267304
function setYieldInterval(uint256 yieldInterval_) external onlyOwner {
268305
if (yieldInterval_ > 30 days) revert MaxYieldInterval();
269306
yieldInterval = yieldInterval_;
@@ -273,10 +310,22 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
273310
PRIVATE YIELD LOCKERS LOGIC
274311
////////////////////////////////////////////////////////////// */
275312

313+
/**
314+
* @notice Marks an address as a private locker.
315+
* @param locker The address of the locker to mark as private.
316+
*/
276317
function addPrivateLocker(address locker) external onlyOwner {
318+
for (uint256 i; i < yieldLockers.length; ++i) {
319+
if (locker == yieldLockers[i]) revert IsActiveLocker();
320+
}
277321
isPrivateLocker[locker] = true;
278322
}
279323

324+
/**
325+
* @notice Deposits an amount into a private locker.
326+
* @param locker The address of the private locker.
327+
* @param amount The amount to deposit.
328+
*/
280329
function depositInPrivateLocker(address locker, uint256 amount) external onlyOwner {
281330
if (isPrivateLocker[locker] == false) revert LockerNotPrivate();
282331

@@ -285,8 +334,26 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
285334
ILocker(locker).deposit(address(underlying()), amount);
286335
}
287336

288-
function collectYieldFromPrivateLocker(address locker) external onlyOwner {
337+
/**
338+
* @notice Collects yield from a private locker.
339+
* @param locker The address of the private locker.
340+
*/
341+
function collectYieldFromPrivateLocker(address locker) external onlyOwner returns (uint256 yield) {
289342
if (isPrivateLocker[locker] == false) revert LockerNotPrivate();
343+
344+
// Cache value
345+
address underlying_ = address(underlying());
346+
// Get total balance before collecting yield.
347+
uint256 initBalance = IERC20(underlying_).balanceOf(address(this));
348+
ILocker(locker).collectYield(underlying_);
349+
// Calculate yield collected.
350+
uint256 newBalance = IERC20(underlying_).balanceOf(address(this));
351+
352+
yield = newBalance > initBalance ? newBalance - initBalance : 0;
353+
if (yield > 0) {
354+
// Mint the yield generated to the treasury.
355+
_mint(treasury, yield);
356+
}
290357
}
291358
// Note : Do we put a recover function (yes with limited withdrawable assets) ?
292359
}

src/token/Storage.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ contract Storage {
1919
STORAGE
2020
////////////////////////////////////////////////////////////// */
2121

22+
// Reentrancy guard.
23+
uint256 internal locked = 1;
2224
// The address of the CardFactory contract.
2325
ICardFactory public cardFactory;
2426
// The address of the treasury

test/Base.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ abstract contract Base_Test is Test {
3333
users = Users({
3434
dao: createUser("dao"),
3535
treasury: createUser("treasury"),
36-
unprivilegedAddress: createUser("unprivilegedAddress")
36+
unprivilegedAddress: createUser("unprivilegedAddress"),
37+
tokenHolder: createUser("tokenHolder")
3738
});
3839
}
3940

test/fuzz/EurB/_EurB.fuzz.t.sol

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.22;
33

4+
import {FixedPointMathLib} from "../../../lib/solmate/src/utils/FixedPointMathLib.sol";
45
import {Fuzz_Test} from "../Fuzz.t.sol";
6+
import {LockerMock} from "../../utils/mocks/LockerMock.sol";
57

68
/**
79
* @notice Common logic needed by all "EurB" fuzz tests.
810
*/
911
abstract contract EurB_Fuzz_Test is Fuzz_Test {
12+
using FixedPointMathLib for uint256;
1013
/* ///////////////////////////////////////////////////////////////
1114
VARIABLES
1215
/////////////////////////////////////////////////////////////// */
1316

17+
struct StateVars {
18+
uint256 idleRatio;
19+
uint256 idleBalance;
20+
uint256 lastSyncTime;
21+
uint256 lastYieldClaim;
22+
uint256 yieldInterval;
23+
}
24+
1425
/* ///////////////////////////////////////////////////////////////
1526
TEST CONTRACTS
1627
/////////////////////////////////////////////////////////////// */
@@ -26,4 +37,80 @@ abstract contract EurB_Fuzz_Test is Fuzz_Test {
2637
/* ///////////////////////////////////////////////////////////////
2738
HELPER FUNCTIONS
2839
/////////////////////////////////////////////////////////////// */
40+
41+
function setStateOfLockers(
42+
uint256 numberOfLockers,
43+
uint256[5] memory lockerDeposits,
44+
uint256[5] memory lockerYields,
45+
uint256[5] memory lockerWeights
46+
) public {
47+
// Given: Deploy lockers and add them to EurB.
48+
numberOfLockers = bound(numberOfLockers, 1, 5);
49+
50+
uint256 sumOfLockerWeights;
51+
uint256[] memory lockerWeights_ = new uint256[](numberOfLockers);
52+
vm.startPrank(users.dao);
53+
for (uint256 i; i < numberOfLockers; ++i) {
54+
// Add locker to EURB.
55+
LockerMock newLocker = new LockerMock(address(EURB));
56+
EURB.addYieldLocker(address(newLocker));
57+
58+
// Set weights.
59+
if (sumOfLockerWeights == BIPS) {
60+
lockerWeights_[i] = 0;
61+
} else {
62+
if (i == numberOfLockers - 1) {
63+
lockerWeights_[i] = BIPS - sumOfLockerWeights;
64+
} else {
65+
lockerWeights_[i] = bound(lockerWeights[i], 0, BIPS - sumOfLockerWeights);
66+
}
67+
}
68+
69+
// Deposit in lockers.
70+
lockerDeposits[i] = bound(lockerDeposits[i], 0, type(uint96).max);
71+
// Mint to locker and dao.
72+
EURE.mint(address(newLocker), lockerDeposits[i]);
73+
EURB.mint(users.dao, lockerDeposits[i]);
74+
// Increase locker deposit.
75+
newLocker.increaseDeposits(lockerDeposits[i]);
76+
// Send the yield to the locker.
77+
// Yield should be max 20 % of deposited value.
78+
lockerYields[i] = bound(lockerYields[i], 0, lockerDeposits[i].mulDivDown(2_000, BIPS));
79+
// Mint the yield to the locker.
80+
EURE.mint(address(newLocker), lockerYields[i]);
81+
}
82+
// Sum of weights should be equal to BIPS.
83+
assertEq(sumOfLockerWeights, BIPS);
84+
// Set weights.
85+
EURB.setWeights(lockerWeights_);
86+
vm.stopPrank();
87+
}
88+
89+
function setStateVars(StateVars memory stateVars) public {
90+
vm.startPrank(users.dao);
91+
92+
// Set idle ratio.
93+
stateVars.idleRatio = bound(stateVars.idleRatio, 0, BIPS);
94+
EURB.setIdleRatio(stateVars.idleRatio);
95+
96+
// Set last sync time.
97+
stateVars.lastSyncTime = bound(stateVars.lastSyncTime, 0, block.timestamp - 1 days - 1);
98+
EURB.setLastSyncTime(stateVars.lastSyncTime);
99+
100+
// Set last yield interval.
101+
stateVars.yieldInterval = bound(stateVars.yieldInterval, 0, 30 days);
102+
EURB.setYieldInterval(stateVars.yieldInterval);
103+
104+
// Set last yield claim.
105+
stateVars.lastYieldClaim = bound(stateVars.lastYieldClaim, 0, block.timestamp - stateVars.yieldInterval - 1);
106+
EURB.setLastYieldClaim(stateVars.lastYieldClaim);
107+
108+
// Set idle balance.
109+
stateVars.idleBalance = bound(stateVars.idleBalance, 0, type(uint96).max);
110+
EURE.mint(users.dao, stateVars.idleBalance);
111+
vm.startPrank(users.dao);
112+
EURB.depositFor(users.dao, stateVars.idleBalance);
113+
114+
vm.stopPrank();
115+
}
29116
}

test/fuzz/Fuzz.t.sol

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {CardFactoryMock} from "../utils/mocks/CardFactoryMock.sol";
66
import {CommissionModule} from "../../src/modules/CommissionModule.sol";
77
import {ERC20Mock} from "../utils/mocks/ERC20Mock.sol";
88
import {EurB} from "../../src/token/EurB.sol";
9+
import {EurBExtension} from "../utils/extensions/EurBExtension.sol";
910
import {IERC20} from "../../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
11+
import {LockerMock} from "../utils/mocks/LockerMock.sol";
1012
import {SafeMock} from "../utils/mocks/SafeMock.sol";
1113

1214
/**
@@ -17,19 +19,25 @@ abstract contract Fuzz_Test is Base_Test {
1719
VARIABLES
1820
//////////////////////////////////////////////////////////////////////////*/
1921

22+
uint256 public BIPS = 10_000;
23+
// Unix timestamps of 12 November 2024
24+
uint256 public DATE_12_NOV_24 = 1731418562;
25+
2026
/*//////////////////////////////////////////////////////////////////////////
2127
TEST CONTRACTS
2228
//////////////////////////////////////////////////////////////////////////*/
2329

2430
CardFactoryMock public CARD_FACTORY;
2531
CommissionModule public COMMISSION_MODULE;
2632
ERC20Mock public EURE;
27-
EurB public EURB;
33+
EurBExtension public EURB;
2834

2935
SafeMock public SAFE1;
3036
SafeMock public SAFE2;
3137
SafeMock public SAFE3;
3238

39+
LockerMock[] public yieldLockers;
40+
3341
/*//////////////////////////////////////////////////////////////////////////
3442
SET-UP FUNCTION
3543
//////////////////////////////////////////////////////////////////////////*/
@@ -38,14 +46,15 @@ abstract contract Fuzz_Test is Base_Test {
3846
Base_Test.setUp();
3947

4048
// Warp to have a timestamp of at least two days old.
41-
vm.warp(2 days);
49+
vm.warp(DATE_12_NOV_24);
4250

4351
// Deploy contracts.
4452
vm.startPrank(users.dao);
4553
COMMISSION_MODULE = new CommissionModule();
4654
CARD_FACTORY = new CardFactoryMock(address(COMMISSION_MODULE));
55+
CARD_FACTORY.setCommissionHookModule(address(COMMISSION_MODULE));
4756
EURE = new ERC20Mock("Monerium EUR", "EURE", 18);
48-
EURB = new EurB(IERC20(address(EURE)), users.treasury, address(CARD_FACTORY));
57+
EURB = new EurBExtension(IERC20(address(EURE)), users.treasury, address(CARD_FACTORY));
4958

5059
SAFE1 = new SafeMock();
5160
SAFE2 = new SafeMock();

test/utils/Types.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ struct Users {
55
address payable dao;
66
address payable treasury;
77
address payable unprivilegedAddress;
8+
address payable tokenHolder;
89
}

0 commit comments

Comments
 (0)