Skip to content
This repository has been archived by the owner on Aug 26, 2024. It is now read-only.

Add borrow Flywheel and booster #73

Merged
merged 38 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d4b4eca
Add borrow Flywheel and booster
antisaa Jul 8, 2024
0f7255d
Update flywheel contracts
antisaa Jul 10, 2024
18ee388
Add separate valut contract for flywheel rewards
antisaa Jul 15, 2024
9f32aa4
revert last commit and fix code style
antisaa Jul 16, 2024
1a6156c
Merge branch 'development' into add-borrow-flywheel-and-booster
antisaa Jul 17, 2024
6b1a16c
Add flywheel deploy script
antisaa Jul 17, 2024
963065d
fix: remappings
rhlsthrm Jul 18, 2024
f3770e0
remove flywheel-v2 library, add missing contracts and fix tests
antisaa Jul 18, 2024
43db358
update solidity, move rest of files and update tests
antisaa Jul 18, 2024
e293a03
Merge branch 'development' into add-borrow-flywheel-and-booster
rhlsthrm Jul 19, 2024
9f023fd
fix: index
rhlsthrm Jul 22, 2024
c6254a1
Update script
antisaa Jul 22, 2024
bf99e80
Update Ionic flywheel dynamic rewards
antisaa Jul 22, 2024
e88c0c6
fix: deploy
rhlsthrm Jul 22, 2024
ebcd9cf
Merge branch 'add-borrow-flywheel-and-booster' of github.com:ionicpro…
rhlsthrm Jul 22, 2024
3d3a0bf
fix: scripts
rhlsthrm Jul 22, 2024
e4e9f00
fix: deploy
rhlsthrm Jul 22, 2024
60d0feb
fix: naming
rhlsthrm Jul 22, 2024
cdbfd9b
fix: script
rhlsthrm Jul 22, 2024
8c66bde
fix: deploy
rhlsthrm Jul 22, 2024
f6887c0
fix deploy
antisaa Jul 22, 2024
7e9722d
fix: artfiact
rhlsthrm Jul 22, 2024
29a95fe
fix: script
rhlsthrm Jul 22, 2024
1f21a04
Add cToken upgrade
antisaa Jul 22, 2024
94eb566
feat: base deploy
rhlsthrm Jul 23, 2024
810d917
fix: underlying
rhlsthrm Jul 23, 2024
a886f8d
Add check before setting strategy
antisaa Jul 23, 2024
d22d2bc
fix: casing
rhlsthrm Jul 23, 2024
11d6d36
Add reward distributor check before setup
antisaa Jul 23, 2024
4b75b20
fix: console
rhlsthrm Jul 23, 2024
3630180
fix: abi
rhlsthrm Jul 23, 2024
afbdb0c
fix: smol
rhlsthrm Jul 23, 2024
5a1b45d
Update upgrade script
antisaa Jul 23, 2024
8a605f9
fix: deploys
rhlsthrm Jul 23, 2024
dd12e56
Update script
antisaa Jul 24, 2024
2aace37
fix: task
rhlsthrm Jul 24, 2024
67b94d8
Update script
antisaa Jul 24, 2024
0160610
fix: base
rhlsthrm Jul 24, 2024
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
4 changes: 2 additions & 2 deletions contracts/ionic/strategies/IonicERC4626.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ abstract contract IonicERC4626 is SafeOwnableUpgradeable, PausableUpgradeable, E

/* ========== INITIALIZER ========== */

function __MidasER4626_init(ERC20Upgradeable asset_) internal onlyInitializing {
function __IonicER4626_init(ERC20Upgradeable asset_) internal onlyInitializing {
__SafeOwnable_init(msg.sender);
__Pausable_init();
__Context_init();
__ERC20_init(
string(abi.encodePacked("Midas ", asset_.name(), " Vault")),
string(abi.encodePacked("Ionic ", asset_.name(), " Vault")),
string(abi.encodePacked("mv", asset_.symbol()))
);
__ERC4626_init(asset_);
Expand Down
6 changes: 3 additions & 3 deletions contracts/ionic/strategies/MockERC4626Dynamic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ERC20 } from "solmate/tokens/ERC20.sol";

import { ERC4626 } from "solmate/mixins/ERC4626.sol";
import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol";
import { FlywheelCore } from "flywheel-v2/FlywheelCore.sol";
import { IonicFlywheelCore } from "./flywheel/IonicFlywheelCore.sol";

/**
* @title Mock ERC4626 Contract
Expand All @@ -17,7 +17,7 @@ contract MockERC4626Dynamic is ERC4626 {
using FixedPointMathLib for uint256;

/* ========== STATE VARIABLES ========== */
FlywheelCore public immutable flywheel;
IonicFlywheelCore public immutable flywheel;

/* ========== INITIALIZER ========== */

Expand All @@ -26,7 +26,7 @@ contract MockERC4626Dynamic is ERC4626 {
@param _asset The ERC20 compliant token the Vault should accept.
@param _flywheel Flywheel to pull in rewardsToken
*/
constructor(ERC20 _asset, FlywheelCore _flywheel)
constructor(ERC20 _asset, IonicFlywheelCore _flywheel)
ERC4626(
_asset,
string(abi.encodePacked("Midas ", _asset.name(), " Vault")),
Expand Down
272 changes: 272 additions & 0 deletions contracts/ionic/strategies/flywheel/FlywheelCore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.10;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {Auth, Authority} from "solmate/auth/Auth.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {SafeCastLib} from "solmate/utils/SafeCastLib.sol";

import {IFlywheelRewards} from "./rewards/IFlywheelRewards.sol";
import {IFlywheelBooster} from "./IFlywheelBooster.sol";

/**
@title Flywheel Core Incentives Manager
@notice Flywheel is a general framework for managing token incentives.
It takes reward streams to various *strategies* such as staking LP tokens and divides them among *users* of those strategies.

The Core contract maintaings three important pieces of state:
* the rewards index which determines how many rewards are owed per token per strategy. User indexes track how far behind the strategy they are to lazily calculate all catch-up rewards.
* the accrued (unclaimed) rewards per user.
* references to the booster and rewards module described below.

Core does not manage any tokens directly. The rewards module maintains token balances, and approves core to pull transfer them to users when they claim.

SECURITY NOTE: For maximum accuracy and to avoid exploits, rewards accrual should be notified atomically through the accrue hook.
Accrue should be called any time tokens are transferred, minted, or burned.
*/
contract FlywheelCore is Auth {
using SafeTransferLib for ERC20;
using SafeCastLib for uint256;

/// @notice The token to reward
ERC20 public immutable rewardToken;

/// @notice append-only list of strategies added
ERC20[] public allStrategies;

/// @notice the rewards contract for managing streams
IFlywheelRewards public flywheelRewards;

/// @notice optional booster module for calculating virtual balances on strategies
IFlywheelBooster public flywheelBooster;

constructor(
ERC20 _rewardToken,
IFlywheelRewards _flywheelRewards,
IFlywheelBooster _flywheelBooster,
address _owner,
Authority _authority
) Auth(_owner, _authority) {
rewardToken = _rewardToken;
flywheelRewards = _flywheelRewards;
flywheelBooster = _flywheelBooster;
}

/*///////////////////////////////////////////////////////////////
ACCRUE/CLAIM LOGIC
//////////////////////////////////////////////////////////////*/

/**
@notice Emitted when a user's rewards accrue to a given strategy.
@param strategy the updated rewards strategy
@param user the user of the rewards
@param rewardsDelta how many new rewards accrued to the user
@param rewardsIndex the market index for rewards per token accrued
*/
event AccrueRewards(ERC20 indexed strategy, address indexed user, uint256 rewardsDelta, uint256 rewardsIndex);

/**
@notice Emitted when a user claims accrued rewards.
@param user the user of the rewards
@param amount the amount of rewards claimed
*/
event ClaimRewards(address indexed user, uint256 amount);

/// @notice The accrued but not yet transferred rewards for each user
mapping(address => uint256) public rewardsAccrued;

/**
@notice accrue rewards for a single user on a strategy
@param strategy the strategy to accrue a user's rewards on
@param user the user to be accrued
@return the cumulative amount of rewards accrued to user (including prior)
*/
function accrue(ERC20 strategy, address user) public returns (uint256) {
RewardsState memory state = strategyState[strategy];

if (state.index == 0) return 0;

state = accrueStrategy(strategy, state);
return accrueUser(strategy, user, state);
}

/**
@notice accrue rewards for a two users on a strategy
@param strategy the strategy to accrue a user's rewards on
@param user the first user to be accrued
@param user the second user to be accrued
@return the cumulative amount of rewards accrued to the first user (including prior)
@return the cumulative amount of rewards accrued to the second user (including prior)
*/
function accrue(
ERC20 strategy,
address user,
address secondUser
) public returns (uint256, uint256) {
RewardsState memory state = strategyState[strategy];

if (state.index == 0) return (0, 0);

state = accrueStrategy(strategy, state);
return (accrueUser(strategy, user, state), accrueUser(strategy, secondUser, state));
}

/**
@notice claim rewards for a given user
@param user the user claiming rewards
@dev this function is public, and all rewards transfer to the user
*/
function claimRewards(address user) external {
uint256 accrued = rewardsAccrued[user];

if (accrued != 0) {
rewardsAccrued[user] = 0;

rewardToken.safeTransferFrom(address(flywheelRewards), user, accrued);

emit ClaimRewards(user, accrued);
}
}

/*///////////////////////////////////////////////////////////////
ADMIN LOGIC
//////////////////////////////////////////////////////////////*/

/**
@notice Emitted when a new strategy is added to flywheel by the admin
@param newStrategy the new added strategy
*/
event AddStrategy(address indexed newStrategy);

/// @notice initialize a new strategy
function addStrategyForRewards(ERC20 strategy) external requiresAuth {
_addStrategyForRewards(strategy);
}

function _addStrategyForRewards(ERC20 strategy) internal {
require(strategyState[strategy].index == 0, "strategy");
strategyState[strategy] = RewardsState({index: ONE, lastUpdatedTimestamp: block.timestamp.safeCastTo32()});

allStrategies.push(strategy);
emit AddStrategy(address(strategy));
}

function getAllStrategies() external view returns (ERC20[] memory) {
return allStrategies;
}

/**
@notice Emitted when the rewards module changes
@param newFlywheelRewards the new rewards module
*/
event FlywheelRewardsUpdate(address indexed newFlywheelRewards);

/// @notice swap out the flywheel rewards contract
function setFlywheelRewards(IFlywheelRewards newFlywheelRewards) external requiresAuth {
uint256 oldRewardBalance = rewardToken.balanceOf(address(flywheelRewards));
if (oldRewardBalance > 0) {
rewardToken.safeTransferFrom(address(flywheelRewards), address(newFlywheelRewards), oldRewardBalance);
}

flywheelRewards = newFlywheelRewards;

emit FlywheelRewardsUpdate(address(newFlywheelRewards));
}

/**
@notice Emitted when the booster module changes
@param newBooster the new booster module
*/
event FlywheelBoosterUpdate(address indexed newBooster);

/// @notice swap out the flywheel booster contract
function setBooster(IFlywheelBooster newBooster) external requiresAuth {
flywheelBooster = newBooster;

emit FlywheelBoosterUpdate(address(newBooster));
}

/*///////////////////////////////////////////////////////////////
INTERNAL ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/

struct RewardsState {
/// @notice The strategy's last updated index
uint224 index;
/// @notice The timestamp the index was last updated at
uint32 lastUpdatedTimestamp;
}

/// @notice the fixed point factor of flywheel
uint224 public constant ONE = 1e18;

/// @notice The strategy index and last updated per strategy
mapping(ERC20 => RewardsState) public strategyState;

/// @notice user index per strategy
mapping(ERC20 => mapping(address => uint224)) public userIndex;

/// @notice accumulate global rewards on a strategy
function accrueStrategy(ERC20 strategy, RewardsState memory state)
private
returns (RewardsState memory rewardsState)
{
// calculate accrued rewards through module
uint256 strategyRewardsAccrued = flywheelRewards.getAccruedRewards(strategy, state.lastUpdatedTimestamp);

rewardsState = state;
if (strategyRewardsAccrued > 0) {
// use the booster or token supply to calculate reward index denominator
uint256 supplyTokens = address(flywheelBooster) != address(0)
? flywheelBooster.boostedTotalSupply(strategy)
: strategy.totalSupply();

uint224 deltaIndex;

if (supplyTokens != 0) deltaIndex = ((strategyRewardsAccrued * ONE) / supplyTokens).safeCastTo224();

// accumulate rewards per token onto the index, multiplied by fixed-point factor
rewardsState = RewardsState({
index: state.index + deltaIndex,
lastUpdatedTimestamp: block.timestamp.safeCastTo32()
});
strategyState[strategy] = rewardsState;
}
}

/// @notice accumulate rewards on a strategy for a specific user
function accrueUser(
ERC20 strategy,
address user,
RewardsState memory state
) private returns (uint256) {
// load indices
uint224 strategyIndex = state.index;
uint224 supplierIndex = userIndex[strategy][user];

// sync user index to global
userIndex[strategy][user] = strategyIndex;

// if user hasn't yet accrued rewards, grant them interest from the strategy beginning if they have a balance
// zero balances will have no effect other than syncing to global index
if (supplierIndex == 0) {
supplierIndex = ONE;
}

uint224 deltaIndex = strategyIndex - supplierIndex;
// use the booster or token balance to calculate reward balance multiplier
uint256 supplierTokens = address(flywheelBooster) != address(0)
? flywheelBooster.boostedBalanceOf(strategy, user)
: strategy.balanceOf(user);

// accumulate rewards by multiplying user tokens by rewardsPerToken index and adding on unclaimed
uint256 supplierDelta = (supplierTokens * deltaIndex) / ONE;
uint256 supplierAccrued = rewardsAccrued[user] + supplierDelta;

rewardsAccrued[user] = supplierAccrued;

emit AccrueRewards(strategy, user, supplierDelta, strategyIndex);

return supplierAccrued;
}
}
34 changes: 34 additions & 0 deletions contracts/ionic/strategies/flywheel/IFlywheelBooster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.10;

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

/**
@title Balance Booster Module for Flywheel
@notice Flywheel is a general framework for managing token incentives.
It takes reward streams to various *strategies* such as staking LP tokens and divides them among *users* of those strategies.

The Booster module is an optional module for virtually boosting or otherwise transforming user balances.
If a booster is not configured, the strategies ERC-20 balanceOf/totalSupply will be used instead.

Boosting logic can be associated with referrals, vote-escrow, or other strategies.

SECURITY NOTE: similar to how Core needs to be notified any time the strategy user composition changes, the booster would need to be notified of any conditions which change the boosted balances atomically.
This prevents gaming of the reward calculation function by using manipulated balances when accruing.
*/
interface IFlywheelBooster {
/**
@notice calculate the boosted supply of a strategy.
@param strategy the strategy to calculate boosted supply of
@return the boosted supply
*/
function boostedTotalSupply(ERC20 strategy) external view returns (uint256);

/**
@notice calculate the boosted balance of a user in a given strategy.
@param strategy the strategy to calculate boosted balance of
@param user the user to calculate boosted balance of
@return the boosted balance
*/
function boostedBalanceOf(ERC20 strategy, address user) external view returns (uint256);
}
6 changes: 1 addition & 5 deletions contracts/ionic/strategies/flywheel/IIonicFlywheel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ interface IIonicFlywheel {

function flywheelPreBorrowerAction(address market, address borrower) external;

function flywheelPreTransferAction(
address market,
address src,
address dst
) external;
function flywheelPreTransferAction(address market, address src, address dst) external;

function compAccrued(address user) external view returns (uint256);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.10;

import { ICErc20 } from "../../../compound/CTokenInterfaces.sol";

/**
@title Balance Booster Module for Flywheel
@notice Flywheel is a general framework for managing token incentives.
It takes reward streams to various *strategies* such as staking LP tokens and divides them among *users* of those strategies.

The Booster module is an optional module for virtually boosting or otherwise transforming user balances.
If a booster is not configured, the strategies ERC-20 balanceOf/totalSupply will be used instead.

Boosting logic can be associated with referrals, vote-escrow, or other strategies.

SECURITY NOTE: similar to how Core needs to be notified any time the strategy user composition changes, the booster would need to be notified of any conditions which change the boosted balances atomically.
This prevents gaming of the reward calculation function by using manipulated balances when accruing.
*/
interface IIonicFlywheelBorrowBooster {
/**
@notice calculate the boosted supply of a strategy.
@param strategy the strategy to calculate boosted supply of
@return the boosted supply
*/
function boostedTotalSupply(ICErc20 strategy) external view returns (uint256);

/**
@notice calculate the boosted balance of a user in a given strategy.
@param strategy the strategy to calculate boosted balance of
@param user the user to calculate boosted balance of
@return the boosted balance
*/
function boostedBalanceOf(ICErc20 strategy, address user) external view returns (uint256);
}
Loading
Loading