Skip to content

Commit

Permalink
init tests
Browse files Browse the repository at this point in the history
  • Loading branch information
acollette committed Nov 19, 2024
1 parent df81562 commit 52deba5
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/lockers/AaveLocker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ contract AaveV3Locker is AbstractLocker {
CONSTRUCTOR
////////////////////////////////////////////////////////////// */

constructor(address owner_, address aToken) AbstractLocker(owner_) {
constructor(address owner_, address aToken, address aavePool) AbstractLocker(owner_) {
ATOKEN = IAaveToken(aToken);
AAVE_POOL = IAavePool(aavePool);
}

/* //////////////////////////////////////////////////////////////
Expand Down
45 changes: 31 additions & 14 deletions src/token/EurB.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
using SafeERC20 for IERC20;
using FixedPointMathLib for uint256;

// note : yield

/* //////////////////////////////////////////////////////////////
ERRORS
////////////////////////////////////////////////////////////// */

error IsNotALocker();
error LengthMismatch();
error LockerNotPrivate();
error MaxCommissionsDepth();
error MaxRatio();
error MaxYieldInterval();
Expand Down Expand Up @@ -58,8 +57,6 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
ERC20 LOGIC
////////////////////////////////////////////////////////////// */

// Note: overwrite depositFor function and add a syncInterest/collateral

/**
* @notice Moves an amount of tokens from the caller's account to "to".
* @param to The address the tokens are sent to.
Expand Down Expand Up @@ -137,7 +134,8 @@ contract EurB is ERC20Wrapper, Ownable, Storage {

// Check if current idle balance meets target idle balance.
uint256 currentIdle = IERC20(underlying_).balanceOf(address(this));
uint256 targetIdle = totalSupply() - totalInvested;
uint256 totalSupplyExclPrivate = totalSupply() - privateLockersSupply;
uint256 targetIdle = totalSupplyExclPrivate.mulDivDown(idleRatio, BIPS);

// If not, withdraw from lockers according to weigths.
if (currentIdle < targetIdle) {
Expand All @@ -147,20 +145,22 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
for (uint256 i; i < weights.length; i++) {
uint256 proportionalAmount = toWithdraw.mulDivDown(weights[i], BIPS_);
// If locker has not enough balance, withdraw max possible.
lockerBalances[i] >= proportionalAmount
? ILocker(lockers[i]).withdraw(underlying_, proportionalAmount)
: ILocker(lockers[i]).withdraw(underlying_, lockerBalances[i]);
if (lockerBalances[i] >= proportionalAmount) {
try ILocker(lockers[i]).withdraw(underlying_, proportionalAmount) {} catch {}
} else {
try ILocker(lockers[i]).withdraw(underlying_, lockerBalances[i]) {} catch {}
}
}
}

// Get total amount that should be deposited in lockers (non-idle).
// Note : adapt formula for private locker that will have impact on total invested.
// Note : check if ok to keep same totalSupply here (think should be ok)
uint256 totalToInvest = totalSupply().mulDivDown(BIPS_ - idleRatio, BIPS_);
uint256 totalToInvest = totalSupplyExclPrivate.mulDivDown(BIPS_ - idleRatio, BIPS_);

// We use weights.length as those should always sum to BIPS (see setWeights()).
for (uint256 i; i < weights.length; ++i) {
uint256 targetBalance = totalToInvest.mulDivDown(weights[i], BIPS_);
// We have to call totalDeposited() again as balance may have updated after withdrawals.
uint256 currentBalance = ILocker(lockers[i]).totalDeposited();

if (currentBalance < targetBalance) {
Expand Down Expand Up @@ -248,8 +248,6 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
lockersWeights.pop();
}

function addPrivateLocker(address locker) external onlyOwner {}

// Note : Double check no issue if idle set to max vs lockers
function setWeights(uint256[] memory newLockersWeights) external onlyOwner {
if (newLockersWeights.length != yieldLockers.length) revert LengthMismatch();
Expand All @@ -270,6 +268,25 @@ contract EurB is ERC20Wrapper, Ownable, Storage {
if (yieldInterval_ > 30 days) revert MaxYieldInterval();
yieldInterval = yieldInterval_;
}
// Note : add a function to remove a locker.
// Note : Do we put a recover function ?

/* //////////////////////////////////////////////////////////////
PRIVATE YIELD LOCKERS LOGIC
////////////////////////////////////////////////////////////// */

function addPrivateLocker(address locker) external onlyOwner {
isPrivateLocker[locker] = true;
}

function depositInPrivateLocker(address locker, uint256 amount) external onlyOwner {
if (isPrivateLocker[locker] == false) revert LockerNotPrivate();

privateLockersSupply += amount;

ILocker(locker).deposit(address(underlying()), amount);
}

function collectYieldFromPrivateLocker(address locker) external onlyOwner {
if (isPrivateLocker[locker] == false) revert LockerNotPrivate();
}
// Note : Do we put a recover function (yes with limited withdrawable assets) ?
}
7 changes: 6 additions & 1 deletion src/token/Storage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract Storage {
STORAGE
////////////////////////////////////////////////////////////// */

// Would work with a registry here to access hookModules (others added and change in logic)
// The address of the CardFactory contract.
ICardFactory public cardFactory;
// The address of the treasury
address public treasury;
Expand All @@ -35,4 +35,9 @@ contract Storage {
uint256 public lastYieldClaim;
// The minimum window between two yield claims.
uint256 public yieldInterval;
// The total amount deposited in private lockers.
uint256 public privateLockersSupply;

// A mapping indicating if a yield locker is a private locker.
mapping(address locker => bool isPrivate) public isPrivateLocker;
}
2 changes: 1 addition & 1 deletion src/token/interfaces/ISafe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
pragma solidity ^0.8.22;

interface ISafe {
function isModuleEnabled(address module) external returns (bool);
function isModuleEnabled(address module) external view returns (bool);
}
50 changes: 50 additions & 0 deletions test/Base.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {CardFactoryMock} from "./utils/mocks/CardFactoryMock.sol";
import {EurB} from "../src/token/EurB.sol";
import {Test} from "../lib/forge-std/src/Test.sol";
import {Users} from "./utils/Types.sol";

/// @notice Base test contract with common logic needed by all tests.
abstract contract Base_Test is Test {
/*//////////////////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////////////////*/

/*//////////////////////////////////////////////////////////////////////////
VARIABLES
//////////////////////////////////////////////////////////////////////////*/

Users internal users;

/*//////////////////////////////////////////////////////////////////////////
TEST CONTRACTS
//////////////////////////////////////////////////////////////////////////*/

/*//////////////////////////////////////////////////////////////////////////
SET-UP FUNCTION
//////////////////////////////////////////////////////////////////////////*/

constructor() {}

function setUp() public virtual {
// Create users for testing.
users = Users({
dao: createUser("dao"),
treasury: createUser("treasury"),
unprivilegedAddress: createUser("unprivilegedAddress")
});
}

/*//////////////////////////////////////////////////////////////////////////
HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @dev Generates a user, labels its address, and funds it with test assets.
function createUser(string memory name) internal returns (address payable) {
address payable user = payable(makeAddr(name));
vm.deal({account: user, newBalance: 100 ether});
return user;
}
}
1 change: 1 addition & 0 deletions test/fork/Fork.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

37 changes: 37 additions & 0 deletions test/fuzz/EurB/Constructor.fuzz.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {EurB_Fuzz_Test} from "./_EurB.fuzz.t.sol";

import {EurB} from "../../../src/token/EurB.sol";
import {IERC20} from "../../../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";

/**
* @notice Fuzz tests for the function "constructor" of contract "EurB".
*/
contract Constructor_EurB_Fuzz_Test is EurB_Fuzz_Test {
/* ///////////////////////////////////////////////////////////////
SETUP
/////////////////////////////////////////////////////////////// */

function setUp() public override {
EurB_Fuzz_Test.setUp();
}

/*//////////////////////////////////////////////////////////////
TESTS
//////////////////////////////////////////////////////////////*/
function testFuzz_Success_deployment(address treasury) public {
// When: Deploying EURB.
vm.prank(users.dao);
EurB eurB = new EurB(IERC20(address(EURE)), treasury, address(CARD_FACTORY));

// Then: Correct variables should be set.
assertEq(address(eurB.underlying()), address(EURE));
assertEq(eurB.treasury(), treasury);
assertEq(eurB.owner(), users.dao);
assertEq(eurB.name(), "EuroBrussels");
assertEq(eurB.symbol(), "EURB");
assertEq(address(eurB.cardFactory()), address(CARD_FACTORY));
}
}
29 changes: 29 additions & 0 deletions test/fuzz/EurB/_EurB.fuzz.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {Fuzz_Test} from "../Fuzz.t.sol";

/**
* @notice Common logic needed by all "EurB" fuzz tests.
*/
abstract contract EurB_Fuzz_Test is Fuzz_Test {
/* ///////////////////////////////////////////////////////////////
VARIABLES
/////////////////////////////////////////////////////////////// */

/* ///////////////////////////////////////////////////////////////
TEST CONTRACTS
/////////////////////////////////////////////////////////////// */

/* ///////////////////////////////////////////////////////////////
SETUP
/////////////////////////////////////////////////////////////// */

function setUp() public virtual override(Fuzz_Test) {
Fuzz_Test.setUp();
}

/* ///////////////////////////////////////////////////////////////
HELPER FUNCTIONS
/////////////////////////////////////////////////////////////// */
}
59 changes: 59 additions & 0 deletions test/fuzz/Fuzz.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {Base_Test} from "../Base.t.sol";
import {CardFactoryMock} from "../utils/mocks/CardFactoryMock.sol";
import {CommissionModule} from "../../src/modules/CommissionModule.sol";
import {ERC20Mock} from "../utils/mocks/ERC20Mock.sol";
import {EurB} from "../../src/token/EurB.sol";
import {IERC20} from "../../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {SafeMock} from "../utils/mocks/SafeMock.sol";

/**
* @notice Common logic needed by all fuzz tests.
*/
abstract contract Fuzz_Test is Base_Test {
/*//////////////////////////////////////////////////////////////////////////
VARIABLES
//////////////////////////////////////////////////////////////////////////*/

/*//////////////////////////////////////////////////////////////////////////
TEST CONTRACTS
//////////////////////////////////////////////////////////////////////////*/

CardFactoryMock public CARD_FACTORY;
CommissionModule public COMMISSION_MODULE;
ERC20Mock public EURE;
EurB public EURB;

SafeMock public SAFE1;
SafeMock public SAFE2;
SafeMock public SAFE3;

/*//////////////////////////////////////////////////////////////////////////
SET-UP FUNCTION
//////////////////////////////////////////////////////////////////////////*/

function setUp() public virtual override {
Base_Test.setUp();

// Warp to have a timestamp of at least two days old.
vm.warp(2 days);

// Deploy contracts.
vm.startPrank(users.dao);
COMMISSION_MODULE = new CommissionModule();
CARD_FACTORY = new CardFactoryMock(address(COMMISSION_MODULE));
EURE = new ERC20Mock("Monerium EUR", "EURE", 18);
EURB = new EurB(IERC20(address(EURE)), users.treasury, address(CARD_FACTORY));

SAFE1 = new SafeMock();
SAFE2 = new SafeMock();
SAFE3 = new SafeMock();
vm.stopPrank();
}

/*//////////////////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////////////////*/
}
8 changes: 8 additions & 0 deletions test/utils/Types.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

struct Users {
address payable dao;
address payable treasury;
address payable unprivilegedAddress;
}
2 changes: 2 additions & 0 deletions test/utils/mocks/ATokenMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
2 changes: 2 additions & 0 deletions test/utils/mocks/AavePoolMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
14 changes: 14 additions & 0 deletions test/utils/mocks/CardFactoryMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

contract CardFactoryMock {
address public COMMISSION_HOOK_MODULE;

constructor(address commissionHookModule) {
COMMISSION_HOOK_MODULE = commissionHookModule;
}

function setCommissionHookModule(address commissionHookModule) public {
COMMISSION_HOOK_MODULE = commissionHookModule;
}
}
18 changes: 18 additions & 0 deletions test/utils/mocks/ERC20Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {ERC20} from "../../../lib/solmate/src/tokens/ERC20.sol";

contract ERC20Mock is ERC20 {
constructor(string memory name_, string memory symbol_, uint8 decimalsInput_)
ERC20(name_, symbol_, decimalsInput_)
{}

function mint(address to, uint256 amount) public {
_mint(to, amount);
}

function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
18 changes: 18 additions & 0 deletions test/utils/mocks/SafeMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

contract SafeMock {
mapping(address module => bool enabled) public moduleEnabled;

function setModule(address module) external {
moduleEnabled[module] = true;
}

function removeModule(address module) external {
moduleEnabled[module] = false;
}

function isModuleEnabled(address module) external view returns (bool enabled) {
enabled = moduleEnabled[module];
}
}

0 comments on commit 52deba5

Please sign in to comment.