Skip to content

Commit 9c26c06

Browse files
committed
♻️ split contracts
1 parent e276852 commit 9c26c06

File tree

3 files changed

+352
-280
lines changed

3 files changed

+352
-280
lines changed

src/token/EurB.sol

+45-272
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,50 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.22;
33

4+
import {AccessControl} from "../../lib/openzeppelin-contracts/contracts/access/AccessControl.sol";
45
import {ERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
5-
import {ERC20Wrapper} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
66
import {FixedPointMathLib} from "../../lib/solmate/src/utils/FixedPointMathLib.sol";
77
import {ICardFactory} from "./interfaces/ICardFactory.sol";
88
import {ICommissionModule} from "./interfaces/ICommissionModule.sol";
99
import {IERC20} from "../../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
1010
import {ISafe} from "./interfaces/ISafe.sol";
11-
import {ILocker} from "../lockers/interfaces/ILocker.sol";
12-
import {Ownable} from "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol";
1311
import {ReentrancyGuard} from "../../lib/solmate/src/utils/ReentrancyGuard.sol";
1412
import {SafeERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
15-
import {Storage} from "./Storage.sol";
1613

17-
contract EurB is ERC20Wrapper, Ownable, Storage {
14+
contract EurB is ERC20, AccessControl {
1815
using SafeERC20 for IERC20;
1916
using FixedPointMathLib for uint256;
2017

18+
/* //////////////////////////////////////////////////////////////
19+
CONSTANTS
20+
////////////////////////////////////////////////////////////// */
21+
22+
// Define Admin Role.
23+
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
24+
// Define Minter Role.
25+
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
26+
// Define Burner Role.
27+
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
28+
29+
// Max number of recursive calls for commissions.
30+
uint256 internal constant MAX_COMMISSIONS_DEPTH = 5;
31+
// 1 BIPS = 0,01%
32+
uint256 internal constant BIPS = 10_000;
33+
34+
/* //////////////////////////////////////////////////////////////
35+
STORAGE
36+
////////////////////////////////////////////////////////////// */
37+
38+
// The address of the Card Factory.
39+
ICardFactory internal cardFactory;
40+
2141
/* //////////////////////////////////////////////////////////////
2242
ERRORS
2343
////////////////////////////////////////////////////////////// */
2444

25-
error IsNotALocker();
26-
error IsActiveLocker();
2745
error LengthMismatch();
28-
error LockerNotPrivate();
2946
error MaxCommissionsDepth();
30-
error MaxRatio();
31-
error MaxYieldInterval();
32-
error MaxYieldLockers();
3347
error RecoveryNotAllowed();
34-
error SyncIntervalNotMet();
35-
error WeightsNotValid();
36-
error YieldIntervalNotMet();
3748

3849
/* //////////////////////////////////////////////////////////////
3950
EVENTS
@@ -43,33 +54,32 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
4354
MODIFIERS
4455
////////////////////////////////////////////////////////////// */
4556

46-
modifier nonReentrant() {
47-
require(locked == 1, "REENTRANCY");
48-
49-
locked = 2;
50-
51-
_;
52-
53-
locked = 1;
54-
}
55-
5657
/* //////////////////////////////////////////////////////////////
5758
CONSTRUCTOR
5859
////////////////////////////////////////////////////////////// */
5960

60-
constructor(IERC20 underlyingToken, address treasury_, address cardFactory_)
61-
ERC20Wrapper(underlyingToken)
62-
ERC20("EuroBrussels", "EURB")
63-
Ownable(msg.sender)
64-
{
65-
treasury = treasury_;
61+
constructor(address cardFactory_) ERC20("EuroBrussels", "EURB") {
62+
_grantRole(ADMIN_ROLE, msg.sender);
6663
cardFactory = ICardFactory(cardFactory_);
6764
}
6865

6966
/* //////////////////////////////////////////////////////////////
7067
ERC20 LOGIC
7168
////////////////////////////////////////////////////////////// */
7269

70+
/**
71+
* @notice Mints an amount of tokens to a specific address.
72+
* @param to The address the tokens are minted for.
73+
* @param amount The amount of tokens to mint.
74+
*/
75+
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
76+
_mint(to, amount);
77+
}
78+
79+
function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) {
80+
_burn(from, amount);
81+
}
82+
7383
/**
7484
* @notice Moves an amount of tokens from the caller's account to "to".
7585
* @param to The address the tokens are sent to.
@@ -132,251 +142,15 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
132142
}
133143

134144
/* //////////////////////////////////////////////////////////////
135-
YIELD LOCKERS LOGIC
145+
ADMIN FUNCTIONS
136146
////////////////////////////////////////////////////////////// */
137147

138148
/**
139-
* @notice Synchronizes all yield lockers by adjusting balances based on weights and idle ratio.
149+
* @notice This function will update the cardFactory address.
150+
* @param cardFactory_ The address of the new cardFactory.
140151
*/
141-
function syncAll() external nonReentrant {
142-
if (block.timestamp < lastSyncTime + 1 days) revert SyncIntervalNotMet();
143-
lastSyncTime = block.timestamp;
144-
145-
// Cache values.
146-
uint256 BIPS_ = BIPS;
147-
address[] memory lockers = yieldLockers;
148-
uint256[] memory weights = lockersWeights;
149-
address underlying_ = address(underlying());
150-
151-
uint256 totalInvested;
152-
uint256[] memory lockerBalances = new uint256[](weights.length);
153-
// We use weights.length as those should always sum to BIPS (see setWeights()).
154-
for (uint256 i; i < weights.length; ++i) {
155-
lockerBalances[i] = ILocker(lockers[i]).totalDeposited();
156-
totalInvested += lockerBalances[i];
157-
}
158-
159-
// Calculate total supply excluding private funds and target values.
160-
uint256 currentIdle = IERC20(underlying_).balanceOf(address(this));
161-
uint256 totalSupplyExclPrivate = currentIdle + totalInvested;
162-
uint256 totalToInvest = totalSupplyExclPrivate.mulDivDown(BIPS_ - idleRatio, BIPS_);
163-
164-
// Step 1: Withdraw excess from overfunded lockers.
165-
for (uint256 i; i < weights.length; ++i) {
166-
uint256 targetBalance = totalToInvest.mulDivDown(weights[i], BIPS_);
167-
uint256 currentBalance = lockerBalances[i];
168-
169-
if (currentBalance > targetBalance) {
170-
uint256 excess = currentBalance - targetBalance;
171-
try ILocker(lockers[i]).withdraw(underlying_, excess) {
172-
// Add withdrawn amount to idle balance.
173-
currentIdle += excess;
174-
} catch {}
175-
}
176-
}
177-
178-
// Step 2: Deposit idle funds into underfunded lockers.
179-
for (uint256 i; i < weights.length; ++i) {
180-
uint256 targetBalance = totalToInvest.mulDivDown(weights[i], BIPS_);
181-
address locker = lockers[i];
182-
uint256 currentBalance = ILocker(locker).totalDeposited();
183-
184-
if (currentBalance < targetBalance) {
185-
uint256 toDeposit = targetBalance - currentBalance;
186-
uint256 depositAmount = currentIdle >= toDeposit ? toDeposit : currentIdle;
187-
188-
if (depositAmount > 0) {
189-
// Avoid redundant approvals.
190-
if (IERC20(underlying_).allowance(address(this), locker) < toDeposit) {
191-
IERC20(underlying_).approve(locker, type(uint256).max);
192-
}
193-
try ILocker(locker).deposit(underlying_, depositAmount) {
194-
currentIdle -= depositAmount;
195-
} catch {}
196-
}
197-
}
198-
}
199-
// At this point, the idle balance should match the target idle balance.
200-
}
201-
202-
/**
203-
* @notice Collects yield from all yield lockers and mints it to the treasury.
204-
* @return yield The total yield collected.
205-
*/
206-
function collectYield() external returns (uint256 yield) {
207-
if (block.timestamp - lastYieldClaim < yieldInterval) revert YieldIntervalNotMet();
208-
209-
// Cache value
210-
address underlying_ = address(underlying());
211-
// Get total balance before collecting yield.
212-
uint256 initBalance = IERC20(underlying_).balanceOf(address(this));
213-
for (uint256 i; i < lockersWeights.length; ++i) {
214-
ILocker(yieldLockers[i]).collectYield(underlying_);
215-
}
216-
// Calculate yield collected.
217-
uint256 newBalance = IERC20(underlying_).balanceOf(address(this));
218-
219-
yield = newBalance > initBalance ? newBalance - initBalance : 0;
220-
if (yield > 0) {
221-
// Mint the yield generated to the treasury.
222-
_mint(treasury, yield);
223-
}
224-
}
225-
226-
/**
227-
* @notice Will set a new treasury.
228-
* @param treasury_ The new treasury address.
229-
*/
230-
function setTreasury(address treasury_) external onlyOwner {
231-
treasury = treasury_;
232-
}
233-
234-
/**
235-
* @notice Adds a new yield locker to the system.
236-
* @param locker The address of the locker to add.
237-
*/
238-
function addYieldLocker(address locker) external onlyOwner {
239-
if (yieldLockers.length == MAX_YIELD_LOCKERS) revert MaxYieldLockers();
240-
yieldLockers.push(locker);
241-
}
242-
243-
/**
244-
* @notice Removes a yield locker from the system, withdrawing its balance.
245-
* @param locker The address of the locker to remove.
246-
*/
247-
function removeYieldLocker(address locker) external onlyOwner {
248-
// Cache values
249-
address[] memory yieldLockers_ = yieldLockers;
250-
if (yieldLockers_.length != lockersWeights.length) revert LengthMismatch();
251-
252-
// Check if locker exists and get the index.
253-
uint256 index;
254-
bool isLocker;
255-
for (uint256 i; i < yieldLockers_.length; ++i) {
256-
if (yieldLockers[i] == locker) {
257-
index = i;
258-
isLocker = true;
259-
}
260-
}
261-
if (isLocker == false) revert IsNotALocker();
262-
263-
// Ensure locker is empty before removal, if not do a fullWithdraw.
264-
address underlying_ = address(underlying());
265-
if (ILocker(locker).getTotalValue(underlying_) != 0) {
266-
(, uint256 yield) = ILocker(locker).fullWithdraw(underlying_);
267-
// Yield collected is minted to the treasury.
268-
if (yield > 0) _mint(treasury, yield);
269-
}
270-
271-
// Replace the locker at index to remove by the locker at last position of array.
272-
if (index < yieldLockers_.length - 1) {
273-
yieldLockers[index] = yieldLockers[yieldLockers_.length - 1];
274-
lockersWeights[index] = lockersWeights[yieldLockers_.length - 1];
275-
}
276-
277-
yieldLockers.pop();
278-
lockersWeights.pop();
279-
}
280-
281-
/**
282-
* @notice Sets the weights for yield lockers.
283-
* @param newLockersWeights The new weights for each locker.
284-
*/
285-
// Note : Double check no issue if idle set to max vs lockers
286-
function setWeights(uint256[] memory newLockersWeights) external onlyOwner {
287-
if (newLockersWeights.length != yieldLockers.length) revert LengthMismatch();
288-
uint256 totalLockerWeights;
289-
for (uint256 i; i < newLockersWeights.length; ++i) {
290-
totalLockerWeights += newLockersWeights[i];
291-
}
292-
if (totalLockerWeights != BIPS) revert WeightsNotValid();
293-
lockersWeights = newLockersWeights;
294-
}
295-
296-
/**
297-
* @notice Sets a new idle ratio for the system.
298-
* @param newRatio The new idle ratio.
299-
*/
300-
function setIdleRatio(uint256 newRatio) external onlyOwner {
301-
if (newRatio > BIPS) revert MaxRatio();
302-
idleRatio = newRatio;
303-
}
304-
305-
/**
306-
* @notice Sets the interval for yield collection.
307-
* @param yieldInterval_ The new yield interval in seconds.
308-
*/
309-
function setYieldInterval(uint256 yieldInterval_) external onlyOwner {
310-
if (yieldInterval_ > 30 days) revert MaxYieldInterval();
311-
yieldInterval = yieldInterval_;
312-
}
313-
314-
/* //////////////////////////////////////////////////////////////
315-
PRIVATE YIELD LOCKERS LOGIC
316-
////////////////////////////////////////////////////////////// */
317-
318-
/**
319-
* @notice Marks an address as a private locker.
320-
* @param locker The address of the locker to mark as private.
321-
*/
322-
function addPrivateLocker(address locker) external onlyOwner {
323-
for (uint256 i; i < yieldLockers.length; ++i) {
324-
if (locker == yieldLockers[i]) revert IsActiveLocker();
325-
}
326-
isPrivateLocker[locker] = true;
327-
}
328-
329-
/**
330-
* @notice Deposits an amount into a private locker.
331-
* @param locker The address of the private locker.
332-
* @param amount The amount to deposit.
333-
*/
334-
function depositInPrivateLocker(address locker, uint256 amount) external onlyOwner {
335-
if (isPrivateLocker[locker] == false) revert LockerNotPrivate();
336-
337-
privateLockersSupply += amount;
338-
339-
underlying().approve(locker, amount);
340-
ILocker(locker).deposit(address(underlying()), amount);
341-
}
342-
343-
/**
344-
* @notice Withdraws an amount from a private locker.
345-
* @param locker The address of the private locker.
346-
* @param amount The amount to withdraw.
347-
*/
348-
function withdrawFromPrivateLocker(address locker, uint256 amount) external onlyOwner {
349-
if (isPrivateLocker[locker] == false) revert LockerNotPrivate();
350-
351-
if (amount > privateLockersSupply) {
352-
privateLockersSupply = 0;
353-
} else {
354-
privateLockersSupply -= amount;
355-
}
356-
357-
ILocker(locker).withdraw(address(underlying()), amount);
358-
}
359-
360-
/**
361-
* @notice Collects yield from a private locker.
362-
* @param locker The address of the private locker.
363-
*/
364-
function collectYieldFromPrivateLocker(address locker) external onlyOwner returns (uint256 yield) {
365-
if (isPrivateLocker[locker] == false) revert LockerNotPrivate();
366-
367-
// Cache value
368-
address underlying_ = address(underlying());
369-
// Get total balance before collecting yield.
370-
uint256 initBalance = IERC20(underlying_).balanceOf(address(this));
371-
ILocker(locker).collectYield(underlying_);
372-
// Calculate yield collected.
373-
uint256 newBalance = IERC20(underlying_).balanceOf(address(this));
374-
375-
yield = newBalance > initBalance ? newBalance - initBalance : 0;
376-
if (yield > 0) {
377-
// Mint the yield generated to the treasury.
378-
_mint(treasury, yield);
379-
}
152+
function setCardFactory(address cardFactory_) external onlyOwner {
153+
cardFactory = ICardFactory(cardFactory_);
380154
}
381155

382156
/* //////////////////////////////////////////////////////////////
@@ -389,7 +163,6 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
389163
* @param amount The amount of asset to recover.
390164
*/
391165
function recoverERC20(address asset, uint256 amount) external onlyOwner {
392-
if (asset == address(underlying())) revert RecoveryNotAllowed();
393166
if (asset == address(this)) revert RecoveryNotAllowed();
394167

395168
IERC20(asset).safeTransfer(msg.sender, amount);

0 commit comments

Comments
 (0)