Skip to content

Commit

Permalink
natspec
Browse files Browse the repository at this point in the history
  • Loading branch information
Joey Santoro committed Mar 20, 2022
1 parent 20966c2 commit 60443f8
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 69 deletions.
177 changes: 117 additions & 60 deletions src/FlywheelCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,21 @@ import {IFlywheelBooster} from "./interfaces/IFlywheelBooster.sol";
/**
@title Flywheel Core Incentives Manager
@notice Flywheel is a general framework for managing token incentives.
It is comprised of the Core (this contract), Rewards module, and optional Booster module.
It takes reward streams to various *strategies* such as staking LP tokens and divides them among *users* of those strategies.
Core is responsible for maintaining reward accrual through reward indexes.
It delegates the actual accrual logic to the Rewards Module.
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.
For maximum accuracy and to avoid exploits, rewards accrual should be notified atomically through the accrue hook.
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;

event AddStrategy(address indexed newStrategy);

event FlywheelRewardsUpdate(address indexed newFlywheelRewards);

event FlywheelBoosterUpdate(address indexed newBooster);

event AccrueRewards(ERC20 indexed cToken, address indexed owner, uint rewardsDelta, uint rewardsIndex);

event ClaimRewards(address indexed owner, uint256 amount);

struct RewardsState {
/// @notice The strategy's last updated index
uint224 index;

/// @notice The timestamp the index was last updated at
uint32 lastUpdatedTimestamp;
}

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

Expand All @@ -49,18 +35,6 @@ contract FlywheelCore is Auth {
/// @notice optional booster module for calculating virtual balances on strategies
IFlywheelBooster public flywheelBooster;

/// @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 The accrued but not yet transferred rewards for each user
mapping(address => uint256) public rewardsAccrued;

constructor(
ERC20 _rewardToken,
IFlywheelRewards _flywheelRewards,
Expand All @@ -73,6 +47,88 @@ contract FlywheelCore is Auth {
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, uint rewardsDelta, uint 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 {
require(strategyState[strategy].index == 0, "strategy");
Expand All @@ -84,52 +140,53 @@ contract FlywheelCore is Auth {
emit AddStrategy(address(strategy));
}

/**
@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 {
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));
}

/// @notice accrue rewards for a single user on a strategy
function accrue(ERC20 strategy, address user) public returns (uint256) {
RewardsState memory state = strategyState[strategy];

if (state.index == 0) return 0;
/*///////////////////////////////////////////////////////////////
INTERNAL ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/

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

/// @notice accrue rewards for two users on a strategy
function accrue(ERC20 strategy, address user, address secondUser) public returns (uint256, uint256) {
RewardsState memory state = strategyState[strategy];

if (state.index == 0) return (0, 0);
struct RewardsState {
/// @notice The strategy's last updated index
uint224 index;

state = accrueStrategy(strategy, state);
return (accrueUser(strategy, user, state), accrueUser(strategy, secondUser, state));
/// @notice The timestamp the index was last updated at
uint32 lastUpdatedTimestamp;
}

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

/// @notice claim rewards for a given owner
function claimRewards(address owner) external {
uint256 accrued = rewardsAccrued[owner];

if (accrued != 0) {
rewardsAccrued[owner] = 0;
/// @notice The strategy index and last updated per strategy
mapping(ERC20 => RewardsState) public strategyState;

rewardToken.safeTransferFrom(address(flywheelRewards), owner, accrued);
/// @notice user index per strategy
mapping(ERC20 => mapping(address => uint224)) public userIndex;

emit ClaimRewards(owner, accrued);
}
}

/// @notice accumulate global rewards on a strategy
function accrueStrategy(ERC20 strategy, RewardsState memory state) private returns(RewardsState memory rewardsState) {
Expand Down
25 changes: 22 additions & 3 deletions src/interfaces/IFlywheelBooster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,31 @@ import {ERC20} from "solmate/tokens/ERC20.sol";

/**
@title Balance Booster Module for Flywheel
@notice An optional module for virtually boosting user balances. This allows a Flywheel Core to plug into some balance boosting logic.
@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.
Boosting logic can be associated with referrals, vote-escrow, or other strategies. It can even be used to model exotic strategies like borrowing.
*/
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);
}
29 changes: 23 additions & 6 deletions src/interfaces/IFlywheelRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,35 @@ import {FlywheelCore} from "../FlywheelCore.sol";

/**
@title Rewards Module for Flywheel
@notice The rewards module is a minimal interface for determining the quantity of rewards accrued to a flywheel strategy.
@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.
Different module strategies include:
* a static reward rate per second
* a decaying reward rate
* a dynamic just-in-time reward stream
* liquid governance reward delegation
The Rewards module is responsible for:
* determining the ongoing reward amounts to entire strategies (core handles the logic for dividing among users)
* actually holding rewards that are yet to be claimed
The reward stream can follow arbitrary logic as long as the amount of rewards passed to flywheel core has been sent to this contract.
Different module strategies include:
* a static reward rate per second
* a decaying reward rate
* a dynamic just-in-time reward stream
* liquid governance reward delegation (Curve Gauge style)
SECURITY NOTE: The rewards strategy should be smooth and continuous, to prevent gaming the reward distribution by frontrunning.
*/
interface IFlywheelRewards {
/**
@notice calculate the rewards amount accrued to a strategy since the last update.
@param strategy the strategy to accrue rewards for.
@param lastUpdatedTimestamp the last time rewards were accrued for the strategy.
@return rewards the amount of rewards accrued to the market
*/
function getAccruedRewards(ERC20 strategy, uint32 lastUpdatedTimestamp) external returns (uint256 rewards);

/// @notice return the flywheel core address
function flywheel() external view returns(FlywheelCore);

/// @notice return the reward token associated with flywheel core.
function rewardToken() external view returns(ERC20);
}
1 change: 1 addition & 0 deletions src/rewards/BaseFlywheelRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {FlywheelCore} from "../FlywheelCore.sol";
/**
@title Flywheel Reward Module
@notice Determines how many rewards accrue to each strategy globally over a given time period.
@dev approves the flywheel core for the reward token to allow balances to be managed by the module but claimed from core.
*/
abstract contract BaseFlywheelRewards is IFlywheelRewards {
using SafeTransferLib for ERC20;
Expand Down

0 comments on commit 60443f8

Please sign in to comment.