Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: reward streams integration #10

Merged
merged 18 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "./"}]

Expand Down
1 change: 1 addition & 0 deletions lib/reward-streams
Submodule reward-streams added at 66aafc
5 changes: 3 additions & 2 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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
openzeppelin-contracts/=lib/reward-streams/lib/openzeppelin-contracts/contracts
@openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/
68 changes: 68 additions & 0 deletions src/BalanceForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol";
import {IBalanceTracker} from "./interface/IBalanceTracker.sol";

/// @title BalanceForwarderModule
/// @custom:security-contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams
abstract contract BalanceForwarder is IBalanceForwarder {
error NotSupported();

IBalanceTracker public immutable balanceTracker;

mapping(address => bool) internal isBalanceForwarderEnabled;

event EnableBalanceForwarder(address indexed _user);
event DisableBalanceForwarder(address indexed _user);

constructor(address _balanceTracker) {
balanceTracker = IBalanceTracker(_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) {
return address(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];
}

function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal {
if (address(balanceTracker) == address(0)) revert NotSupported();

isBalanceForwarderEnabled[_sender] = true;
IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, _senderBalance, false);

emit EnableBalanceForwarder(_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(address _sender) internal {
if (address(balanceTracker) == address(0)) revert NotSupported();

isBalanceForwarderEnabled[_sender] = false;
IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, 0, false);

emit DisableBalanceForwarder(_sender);
}
}
46 changes: 39 additions & 7 deletions src/FourSixTwoSixAgg.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// 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/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
// @note Do NOT use with rebasing tokens
// @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol
// @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();
Expand Down Expand Up @@ -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();
Expand All @@ -139,6 +141,21 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
_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.
Expand Down Expand Up @@ -391,6 +408,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
/// @dev See {IERC4626-_deposit}.
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override {
totalAssetsDeposited += assets;

super._deposit(caller, receiver, assets, shares);
}

Expand Down Expand Up @@ -537,6 +555,20 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
}
}

/// @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
Expand Down
12 changes: 12 additions & 0 deletions src/interface/IBalanceForwarder.sol
Original file line number Diff line number Diff line change
@@ -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;
}
19 changes: 19 additions & 0 deletions src/interface/IBalanceTracker.sol
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions test/common/FourSixTwoSixAggBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase {
vm.startPrank(deployer);
fourSixTwoSixAgg = new FourSixTwoSixAgg(
evc,
address(0),
address(assetTST),
"assetTST_Agg",
"assetTST_Agg",
Expand Down
Loading
Loading