From 409402f162d9b018b95fcbbbec06c0611ea6b798 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 11:29:59 +0100 Subject: [PATCH 01/18] init: reward streams integration --- src/FourSixTwoSixAgg.sol | 6 ++++-- test/common/FourSixTwoSixAggBase.t.sol | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 6ebeac5a..21523d75 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -7,6 +7,7 @@ import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol" import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; +import {BalanceForwarder} from "./BalanceForwarder.sol"; // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens @@ -14,7 +15,7 @@ import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessContr // @note expired by Yearn v3 ❤️ // TODO addons for reward stream support // TODO custom withdraw queue support -contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { +contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable { using SafeERC20 for IERC20; error Reentrancy(); @@ -106,13 +107,14 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points constructor( IEVC _evc, + address _balanceTracker, address _asset, string memory _name, string memory _symbol, uint256 _initialCashAllocationPoints, address[] memory _initialStrategies, uint256[] memory _initialStrategiesAllocationPoints - ) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { + ) BalanceForwarder(_balanceTracker) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; if (_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index bf3298d2..9730a538 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -24,6 +24,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { vm.startPrank(deployer); fourSixTwoSixAgg = new FourSixTwoSixAgg( evc, + address(0), address(assetTST), "assetTST_Agg", "assetTST_Agg", From a4b1a0f05b20439e3facae1cd7744d739e8efc5f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 13:32:00 +0100 Subject: [PATCH 02/18] push files --- src/BalanceForwarder.sol | 78 +++++++++++++++++++++++++++++ src/interface/IBalanceForwarder.sol | 12 +++++ 2 files changed, 90 insertions(+) create mode 100644 src/BalanceForwarder.sol create mode 100644 src/interface/IBalanceForwarder.sol diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol new file mode 100644 index 00000000..25e0450b --- /dev/null +++ b/src/BalanceForwarder.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; + +/// @title BalanceForwarderModule +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling communication with a balance tracker contract. +abstract contract BalanceForwarder is IBalanceForwarder { + error NotSupported(); + + address public immutable balanceTracker; + + mapping(address => bool) internal isBalanceForwarderEnabled; + + constructor(address _balanceTracker) { + balanceTracker = _balanceTracker; + } + + /// @notice Retrieve the address of rewards contract, tracking changes in account's balances + /// @return The balance tracker address + function balanceTrackerAddress() external view returns (address) { + return balanceTracker; + } + + /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract + /// @param _account Address to query + /// @return True if balance forwarder is enabled + function balanceForwarderEnabled(address _account) external view returns (bool) { + return isBalanceForwarderEnabled[_account]; + } + + /// @notice Enables balance forwarding for the authenticated account + /// @dev Only the authenticated account can enable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the current account's balance + function enableBalanceForwarder() external virtual { + _enableBalanceForwarder(msg.sender); + } + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function disableBalanceForwarder() external virtual { + _disableBalanceForwarder(msg.sender); + } + + function _enableBalanceForwarder(address _sender) internal { + if (balanceTracker == address(0)) revert NotSupported(); + + // address account = EVCAuthenticate(); + // UserStorage storage user = vaultStorage.users[account]; + + // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); + + // user.setBalanceForwarder(true); + // balanceTracker.balanceTrackerHook(account, user.getBalance().toUint(), false); + + // if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true); + } + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function _disableBalanceForwarder(address _sender) internal { + if (balanceTracker == address(0)) revert NotSupported(); + + // address account = EVCAuthenticate(); + // UserStorage storage user = vaultStorage.users[account]; + + // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); + + // user.setBalanceForwarder(false); + // balanceTracker.balanceTrackerHook(account, 0, false); + + // if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false); + } +} diff --git a/src/interface/IBalanceForwarder.sol b/src/interface/IBalanceForwarder.sol new file mode 100644 index 00000000..e88cdae1 --- /dev/null +++ b/src/interface/IBalanceForwarder.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +interface IBalanceForwarder { + function balanceTrackerAddress() external view returns (address); + + function balanceForwarderEnabled(address account) external view returns (bool); + + function enableBalanceForwarder() external; + + function disableBalanceForwarder() external; +} From 03b7c10fd9c73c9aaa725918962f172a90d8ad27 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 17:49:32 +0100 Subject: [PATCH 03/18] integrate BalanceForwarder --- src/BalanceForwarder.sol | 52 ++++++++++++++++------------------------ src/FourSixTwoSixAgg.sol | 15 ++++++++++++ 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index 25e0450b..dcb4c30c 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; +import {IBalanceTracker} from "./interface/IBalanceTracker.sol"; /// @title BalanceForwarderModule /// @custom:security-contact security@euler.xyz @@ -14,10 +15,23 @@ abstract contract BalanceForwarder is IBalanceForwarder { mapping(address => bool) internal isBalanceForwarderEnabled; + event EnableBalanceForwarder(address indexed _user); + event DisableBalanceForwarder(address indexed _user); + constructor(address _balanceTracker) { balanceTracker = _balanceTracker; } + /// @notice Enables balance forwarding for the authenticated account + /// @dev Only the authenticated account can enable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the current account's balance + function enableBalanceForwarder() external virtual; + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function disableBalanceForwarder() external virtual; + /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function balanceTrackerAddress() external view returns (address) { @@ -31,32 +45,13 @@ abstract contract BalanceForwarder is IBalanceForwarder { return isBalanceForwarderEnabled[_account]; } - /// @notice Enables balance forwarding for the authenticated account - /// @dev Only the authenticated account can enable balance forwarding for itself - /// @dev Should call the IBalanceTracker hook with the current account's balance - function enableBalanceForwarder() external virtual { - _enableBalanceForwarder(msg.sender); - } - - /// @notice Disables balance forwarding for the authenticated account - /// @dev Only the authenticated account can disable balance forwarding for itself - /// @dev Should call the IBalanceTracker hook with the account's balance of 0 - function disableBalanceForwarder() external virtual { - _disableBalanceForwarder(msg.sender); - } - - function _enableBalanceForwarder(address _sender) internal { + function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { if (balanceTracker == address(0)) revert NotSupported(); - // address account = EVCAuthenticate(); - // UserStorage storage user = vaultStorage.users[account]; - - // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); - - // user.setBalanceForwarder(true); - // balanceTracker.balanceTrackerHook(account, user.getBalance().toUint(), false); + isBalanceForwarderEnabled[_sender] = true; + IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, _senderBalance, false); - // if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true); + emit EnableBalanceForwarder(_sender); } /// @notice Disables balance forwarding for the authenticated account @@ -65,14 +60,9 @@ abstract contract BalanceForwarder is IBalanceForwarder { function _disableBalanceForwarder(address _sender) internal { if (balanceTracker == address(0)) revert NotSupported(); - // address account = EVCAuthenticate(); - // UserStorage storage user = vaultStorage.users[account]; - - // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); - - // user.setBalanceForwarder(false); - // balanceTracker.balanceTrackerHook(account, 0, false); + isBalanceForwarderEnabled[_sender] = false; + IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, 0, false); - // if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false); + emit DisableBalanceForwarder(_sender); } } diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 21523d75..0020528a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -141,6 +141,21 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); } + /// @notice Enables balance forwarding for sender + /// @dev Should call the IBalanceTracker hook with the current user's balance + function enableBalanceForwarder() external override nonReentrant { + address user = _msgSender(); + uint256 userBalance = this.balanceOf(user); + + _enableBalanceForwarder(user, userBalance); + } + + /// @notice Disables balance forwarding for the sender + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function disableBalanceForwarder() external override nonReentrant { + _disableBalanceForwarder(_msgSender()); + } + /// @notice Rebalance strategy allocation. /// @dev This function will first harvest yield, gulps and update interest. /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. From a883e42eb30f39f3cd74aec278fe5ab9aee42f48 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 19:50:38 +0100 Subject: [PATCH 04/18] push files --- src/interface/IBalanceTracker.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/interface/IBalanceTracker.sol diff --git a/src/interface/IBalanceTracker.sol b/src/interface/IBalanceTracker.sol new file mode 100644 index 00000000..82a47d7b --- /dev/null +++ b/src/interface/IBalanceTracker.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +/// @title IBalanceTracker +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Provides an interface for tracking the balance of accounts. +interface IBalanceTracker { + /// @notice Executes the balance tracking hook for an account. + /// @dev This function is called by the Balance Forwarder contract which was enabled for the account. This function + /// must be called with the current balance of the account when enabling the balance forwarding for it. This + /// function must be called with 0 balance of the account when disabling the balance forwarding for it. This + /// function allows to be called on zero balance transfers, when the newAccountBalance is the same as the previous + /// one. To prevent DOS attacks, forfeitRecentReward should be used appropriately. + /// @param account The account address to execute the hook for. + /// @param newAccountBalance The new balance of the account. + /// @param forfeitRecentReward Whether to forfeit the most recent reward and not update the accumulator. + function balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external; +} From 9ceb8dbf708d46f8127399bb4b2c117ad55f2358 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 22 May 2024 14:25:39 +0100 Subject: [PATCH 05/18] IBalanceTracker.balanceTrackerHook() calls --- src/BalanceForwarder.sol | 12 ++++++------ src/FourSixTwoSixAgg.sol | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index dcb4c30c..e0e67622 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -7,11 +7,11 @@ import {IBalanceTracker} from "./interface/IBalanceTracker.sol"; /// @title BalanceForwarderModule /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice An EVault module handling communication with a balance tracker contract. +/// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams abstract contract BalanceForwarder is IBalanceForwarder { error NotSupported(); - address public immutable balanceTracker; + IBalanceTracker public immutable balanceTracker; mapping(address => bool) internal isBalanceForwarderEnabled; @@ -19,7 +19,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { event DisableBalanceForwarder(address indexed _user); constructor(address _balanceTracker) { - balanceTracker = _balanceTracker; + balanceTracker = IBalanceTracker(_balanceTracker); } /// @notice Enables balance forwarding for the authenticated account @@ -35,7 +35,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function balanceTrackerAddress() external view returns (address) { - return balanceTracker; + return address(balanceTracker); } /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract @@ -46,7 +46,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { } function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - if (balanceTracker == address(0)) revert NotSupported(); + if (address(balanceTracker) == address(0)) revert NotSupported(); isBalanceForwarderEnabled[_sender] = true; IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, _senderBalance, false); @@ -58,7 +58,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { - if (balanceTracker == address(0)) revert NotSupported(); + if (address(balanceTracker) == address(0)) revert NotSupported(); isBalanceForwarderEnabled[_sender] = false; IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, 0, false); diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 0020528a..bee67d47 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -408,6 +408,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { totalAssetsDeposited += assets; + + // if (isBalanceForwarderEnabled[receiver]) { + // balanceTracker.balanceTrackerHook(receiver, shares, false); + // } + super._deposit(caller, receiver, assets, shares); } @@ -455,6 +460,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert NotEnoughAssets(); } + // if (isBalanceForwarderEnabled[owner]) { + // balanceTracker.balanceTrackerHook(owner, shares, false); + // } + super._withdraw(caller, receiver, owner, assets, shares); } @@ -554,6 +563,20 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } } + /// @dev Override _afterTokenTransfer hook to call IBalanceTracker.balanceTrackerHook() + /// @dev Calling .balanceTrackerHook() passing the address total balance + /// @param from Address sending the amount + /// @param to Address receiving the amount + function _afterTokenTransfer(address from, address to, uint256 /*amount*/ ) internal override { + if ((from != address(0)) && (isBalanceForwarderEnabled[from])) { + balanceTracker.balanceTrackerHook(from, super.balanceOf(from), false); + } + + if ((to != address(0)) && (isBalanceForwarderEnabled[to])) { + balanceTracker.balanceTrackerHook(to, super.balanceOf(to), false); + } + } + /// @dev Get accrued interest without updating it. /// @param esrSlotCache Cached esrSlot /// @return uint256 accrued interest From fe4015e01b0d288216b10ad14e78886c94a5d615 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 12:31:34 +0100 Subject: [PATCH 06/18] chore: clean --- src/FourSixTwoSixAgg.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index bee67d47..5c5c3d5a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -409,10 +409,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { totalAssetsDeposited += assets; - // if (isBalanceForwarderEnabled[receiver]) { - // balanceTracker.balanceTrackerHook(receiver, shares, false); - // } - super._deposit(caller, receiver, assets, shares); } @@ -460,10 +456,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert NotEnoughAssets(); } - // if (isBalanceForwarderEnabled[owner]) { - // balanceTracker.balanceTrackerHook(owner, shares, false); - // } - super._withdraw(caller, receiver, owner, assets, shares); } From cc141af5fda93377d0ddf2bd5d1b979c79e0b034 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 12:33:00 +0100 Subject: [PATCH 07/18] forge install: reward-streams --- .gitmodules | 3 +++ lib/reward-streams | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 1d3068ab..ae5ee093 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,3 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit +[submodule "lib/reward-streams"] + path = lib/reward-streams + url = https://github.com/euler-xyz/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams new file mode 160000 index 00000000..66aafcd3 --- /dev/null +++ b/lib/reward-streams @@ -0,0 +1 @@ +Subproject commit 66aafcd3b00d01b180648f597218ec3c6c67e34a From cea161da6be204a9b32a8e90b7d8c5132dc3b854 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:20:08 +0100 Subject: [PATCH 08/18] update{ --- remappings.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/remappings.txt b/remappings.txt index f65498dc..7fa1974a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,6 +2,5 @@ ds-test/=lib/ethereum-vault-connector/lib/forge-std/lib/ds-test/src/ erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc4626-tests/ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ -openzeppelin-contracts/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ -openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ evk/=lib/euler-vault-kit/ +reward-streams=lib/reward-streams/src \ No newline at end of file From c47990c017c7e3f41e048e44699b5037822d2f74 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:20:24 +0100 Subject: [PATCH 09/18] add test --- test/e2e/BalanceForwarderE2ETest.t.sol | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/e2e/BalanceForwarderE2ETest.t.sol diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol new file mode 100644 index 00000000..257c4c1c --- /dev/null +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20 +} from "../common/FourSixTwoSixAggBase.t.sol"; +import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; + +contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + address trackingReward; + + function setUp() public virtual override { + super.setUp(); + + vm.startPrank(deployer); + trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); + + fourSixTwoSixAgg = new FourSixTwoSixAgg( + evc, + trackingReward, + address(assetTST), + "assetTST_Agg", + "assetTST_Agg", + CASH_RESERVE_ALLOCATION_POINTS, + new address[](0), + new uint256[](0) + ); + + // grant admin roles to deployer + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); + // grant roles to manager + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); + vm.stopPrank(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + } + + function testBalanceForwarderrAddress_Integrity() public view { + assertEq(fourSixTwoSixAgg.balanceTracker(), trackingReward); + } +} From 6b7a2ba2474d13b4b40439501fc6a3ae29c46ead Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:20:27 +0100 Subject: [PATCH 10/18] forge install: openzeppelin-contracts v5.0.2 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index ae5ee093..80684906 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/reward-streams"] path = lib/reward-streams url = https://github.com/euler-xyz/reward-streams +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 00000000..dbb6104c --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From 5359ff65b5c4e8a2697bdeb9cb36ac36d0c86a06 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:31:42 +0100 Subject: [PATCH 11/18] update paths --- foundry.toml | 2 +- remappings.txt | 3 ++- src/FourSixTwoSixAgg.sol | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/foundry.toml b/foundry.toml index e199dc18..da8c522e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ libs = ["lib"] test = 'test' optimizer = true optimizer_runs = 20_000 -solc = "0.8.23" +# solc = "0.8.0" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/remappings.txt b/remappings.txt index 7fa1974a..b33088d0 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,4 +3,5 @@ erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc46 ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ -reward-streams=lib/reward-streams/src \ No newline at end of file +reward-streams=lib/reward-streams/src +@openzeppelin=lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 5c5c3d5a..51108d22 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Context} from "openzeppelin-contracts/utils/Context.sol"; -import {ERC20, IERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; -import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; -import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; +import {Context} from "@openzeppelin/utils/Context.sol"; +import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; +import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import {ERC4626, IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; +import {AccessControlEnumerable} from "@openzeppelin/access/extensions/AccessControlEnumerable.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; -import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; // @note Do NOT use with fee on transfer tokens From 9eca08ac1cad1c13d1c4718ebe9c8bdee9517ea8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:32:00 +0100 Subject: [PATCH 12/18] remove dep --- .gitmodules | 3 --- lib/reward-streams | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 80684906..f2d28c20 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,9 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit -[submodule "lib/reward-streams"] - path = lib/reward-streams - url = https://github.com/euler-xyz/reward-streams [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/reward-streams b/lib/reward-streams deleted file mode 160000 index 66aafcd3..00000000 --- a/lib/reward-streams +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 66aafcd3b00d01b180648f597218ec3c6c67e34a From 1dc957a0dea5d7d7b647dd6ff308ebcfc9cfe9c8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:37:40 +0100 Subject: [PATCH 13/18] forge install: reward-streams --- .gitmodules | 3 +++ lib/reward-streams | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index f2d28c20..e7747b03 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/reward-streams"] + path = lib/reward-streams + url = https://github.com/haythemsellami/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams new file mode 160000 index 00000000..8df0a643 --- /dev/null +++ b/lib/reward-streams @@ -0,0 +1 @@ +Subproject commit 8df0a6432580302119c7620365b14c29e725f009 From def1be6e9216fa9c6945a71605cd81c9238c9324 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:48:40 +0100 Subject: [PATCH 14/18] fix setup --- remappings.txt | 3 ++- src/FourSixTwoSixAgg.sol | 2 +- test/e2e/BalanceForwarderE2ETest.t.sol | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/remappings.txt b/remappings.txt index b33088d0..be613868 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ reward-streams=lib/reward-streams/src -@openzeppelin=lib/openzeppelin-contracts/contracts/ \ No newline at end of file +openzeppelin-contracts/=lib/openzeppelin-contracts/contracts +@openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 51108d22..eea68b59 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -5,7 +5,7 @@ import {Context} from "@openzeppelin/utils/Context.sol"; import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; import {ERC4626, IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; -import {AccessControlEnumerable} from "@openzeppelin/access/extensions/AccessControlEnumerable.sol"; +import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 257c4c1c..3d7a7bb2 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -53,6 +53,6 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { } function testBalanceForwarderrAddress_Integrity() public view { - assertEq(fourSixTwoSixAgg.balanceTracker(), trackingReward); + assertEq(address(fourSixTwoSixAgg.balanceTracker()), trackingReward); } } From f2f803ea641f4b4cd507e894aad1d47b50a39bff Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:50:32 +0100 Subject: [PATCH 15/18] update --- .gitmodules | 3 --- lib/openzeppelin-contracts | 1 - remappings.txt | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) delete mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index e7747b03..15715476 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,9 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "lib/reward-streams"] path = lib/reward-streams url = https://github.com/haythemsellami/reward-streams diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index dbb6104c..00000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/remappings.txt b/remappings.txt index be613868..1ad598ca 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,5 +4,5 @@ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ reward-streams=lib/reward-streams/src -openzeppelin-contracts/=lib/openzeppelin-contracts/contracts +openzeppelin-contracts/=lib/reward-streams/lib/openzeppelin-contracts/contracts @openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ From 3829aea1bb84cfb22b0fac390208b840a6aa9985 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:50:58 +0100 Subject: [PATCH 16/18] update --- .gitmodules | 3 --- lib/reward-streams | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 15715476..1d3068ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,6 +8,3 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit -[submodule "lib/reward-streams"] - path = lib/reward-streams - url = https://github.com/haythemsellami/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams deleted file mode 160000 index 8df0a643..00000000 --- a/lib/reward-streams +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8df0a6432580302119c7620365b14c29e725f009 From dc5a20da0145f5b4f90901a6e66b8a8cb99956e3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:52:52 +0100 Subject: [PATCH 17/18] forge install: reward-streams --- .gitmodules | 3 +++ lib/reward-streams | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 1d3068ab..ae5ee093 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,3 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit +[submodule "lib/reward-streams"] + path = lib/reward-streams + url = https://github.com/euler-xyz/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams new file mode 160000 index 00000000..66aafcd3 --- /dev/null +++ b/lib/reward-streams @@ -0,0 +1 @@ +Subproject commit 66aafcd3b00d01b180648f597218ec3c6c67e34a From 7b8f703197bdb7545dec475e4333ccd934e915ee Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 22:54:33 +0100 Subject: [PATCH 18/18] more tests --- test/e2e/BalanceForwarderE2ETest.t.sol | 121 ++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 3d7a7bb2..d1597e95 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -48,11 +48,130 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { uint256 initialStrategyAllocationPoints = 500e18; _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); - assetTST.mint(user1, user1InitialBalance); + + // deposit into aggregator + uint256 amountToDeposit = 10000e18; + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } } function testBalanceForwarderrAddress_Integrity() public view { assertEq(address(fourSixTwoSixAgg.balanceTracker()), trackingReward); } + + function testEnableBalanceForwarder() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + assertTrue(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + assertEq( + TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), + fourSixTwoSixAgg.balanceOf(user1) + ); + } + + function testDisableBalanceForwarder() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + assertTrue(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + + vm.prank(user1); + fourSixTwoSixAgg.disableBalanceForwarder(); + + assertFalse(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), 0); + } + + function testHookWhenReceiverEnabled() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + // deposit into aggregator + uint256 amountToDeposit = 10000e18; + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + + assertEq( + TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), + fourSixTwoSixAgg.balanceOf(user1) + ); + } + } + + function testHookWhenSenderEnabled() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + // deposit into aggregator + uint256 amountToDeposit = 10000e18; + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + + assertEq( + TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), + fourSixTwoSixAgg.balanceOf(user1) + ); + } + + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + + assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq( + assetTST.balanceOf(user1), + user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw) + ); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), 0); + } + } }