Skip to content

Commit 60443f8

Browse files
author
Joey Santoro
committed
natspec
1 parent 20966c2 commit 60443f8

File tree

4 files changed

+163
-69
lines changed

4 files changed

+163
-69
lines changed

src/FlywheelCore.sol

Lines changed: 117 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,21 @@ import {IFlywheelBooster} from "./interfaces/IFlywheelBooster.sol";
1111
/**
1212
@title Flywheel Core Incentives Manager
1313
@notice Flywheel is a general framework for managing token incentives.
14-
It is comprised of the Core (this contract), Rewards module, and optional Booster module.
14+
It takes reward streams to various *strategies* such as staking LP tokens and divides them among *users* of those strategies.
1515
16-
Core is responsible for maintaining reward accrual through reward indexes.
17-
It delegates the actual accrual logic to the Rewards Module.
16+
The Core contract maintaings three important pieces of state:
17+
* 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.
18+
* the accrued (unclaimed) rewards per user.
19+
* references to the booster and rewards module described below.
1820
19-
For maximum accuracy and to avoid exploits, rewards accrual should be notified atomically through the accrue hook.
21+
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.
22+
23+
SECURITY NOTE: For maximum accuracy and to avoid exploits, rewards accrual should be notified atomically through the accrue hook.
2024
Accrue should be called any time tokens are transferred, minted, or burned.
2125
*/
2226
contract FlywheelCore is Auth {
2327
using SafeTransferLib for ERC20;
2428

25-
event AddStrategy(address indexed newStrategy);
26-
27-
event FlywheelRewardsUpdate(address indexed newFlywheelRewards);
28-
29-
event FlywheelBoosterUpdate(address indexed newBooster);
30-
31-
event AccrueRewards(ERC20 indexed cToken, address indexed owner, uint rewardsDelta, uint rewardsIndex);
32-
33-
event ClaimRewards(address indexed owner, uint256 amount);
34-
35-
struct RewardsState {
36-
/// @notice The strategy's last updated index
37-
uint224 index;
38-
39-
/// @notice The timestamp the index was last updated at
40-
uint32 lastUpdatedTimestamp;
41-
}
42-
4329
/// @notice The token to reward
4430
ERC20 public immutable rewardToken;
4531

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

52-
/// @notice the fixed point factor of flywheel
53-
uint224 public constant ONE = 1e18;
54-
55-
/// @notice The strategy index and last updated per strategy
56-
mapping(ERC20 => RewardsState) public strategyState;
57-
58-
/// @notice user index per strategy
59-
mapping(ERC20 => mapping(address => uint224)) public userIndex;
60-
61-
/// @notice The accrued but not yet transferred rewards for each user
62-
mapping(address => uint256) public rewardsAccrued;
63-
6438
constructor(
6539
ERC20 _rewardToken,
6640
IFlywheelRewards _flywheelRewards,
@@ -73,6 +47,88 @@ contract FlywheelCore is Auth {
7347
flywheelBooster = _flywheelBooster;
7448
}
7549

50+
/*///////////////////////////////////////////////////////////////
51+
ACCRUE/CLAIM LOGIC
52+
//////////////////////////////////////////////////////////////*/
53+
54+
/**
55+
@notice Emitted when a user's rewards accrue to a given strategy.
56+
@param strategy the updated rewards strategy
57+
@param user the user of the rewards
58+
@param rewardsDelta how many new rewards accrued to the user
59+
@param rewardsIndex the market index for rewards per token accrued
60+
*/
61+
event AccrueRewards(ERC20 indexed strategy, address indexed user, uint rewardsDelta, uint rewardsIndex);
62+
63+
/**
64+
@notice Emitted when a user claims accrued rewards.
65+
@param user the user of the rewards
66+
@param amount the amount of rewards claimed
67+
*/
68+
event ClaimRewards(address indexed user, uint256 amount);
69+
70+
/// @notice The accrued but not yet transferred rewards for each user
71+
mapping(address => uint256) public rewardsAccrued;
72+
73+
/**
74+
@notice accrue rewards for a single user on a strategy
75+
@param strategy the strategy to accrue a user's rewards on
76+
@param user the user to be accrued
77+
@return the cumulative amount of rewards accrued to user (including prior)
78+
*/
79+
function accrue(ERC20 strategy, address user) public returns (uint256) {
80+
RewardsState memory state = strategyState[strategy];
81+
82+
if (state.index == 0) return 0;
83+
84+
state = accrueStrategy(strategy, state);
85+
return accrueUser(strategy, user, state);
86+
}
87+
88+
/**
89+
@notice accrue rewards for a two users on a strategy
90+
@param strategy the strategy to accrue a user's rewards on
91+
@param user the first user to be accrued
92+
@param user the second user to be accrued
93+
@return the cumulative amount of rewards accrued to the first user (including prior)
94+
@return the cumulative amount of rewards accrued to the second user (including prior)
95+
*/
96+
function accrue(ERC20 strategy, address user, address secondUser) public returns (uint256, uint256) {
97+
RewardsState memory state = strategyState[strategy];
98+
99+
if (state.index == 0) return (0, 0);
100+
101+
state = accrueStrategy(strategy, state);
102+
return (accrueUser(strategy, user, state), accrueUser(strategy, secondUser, state));
103+
}
104+
105+
/**
106+
@notice claim rewards for a given user
107+
@param user the user claiming rewards
108+
@dev this function is public, and all rewards transfer to the user
109+
*/
110+
function claimRewards(address user) external {
111+
uint256 accrued = rewardsAccrued[user];
112+
113+
if (accrued != 0) {
114+
rewardsAccrued[user] = 0;
115+
116+
rewardToken.safeTransferFrom(address(flywheelRewards), user, accrued);
117+
118+
emit ClaimRewards(user, accrued);
119+
}
120+
}
121+
122+
/*///////////////////////////////////////////////////////////////
123+
ADMIN LOGIC
124+
//////////////////////////////////////////////////////////////*/
125+
126+
/**
127+
@notice Emitted when a new strategy is added to flywheel by the admin
128+
@param newStrategy the new added strategy
129+
*/
130+
event AddStrategy(address indexed newStrategy);
131+
76132
/// @notice initialize a new strategy
77133
function addStrategyForRewards(ERC20 strategy) external requiresAuth {
78134
require(strategyState[strategy].index == 0, "strategy");
@@ -84,52 +140,53 @@ contract FlywheelCore is Auth {
84140
emit AddStrategy(address(strategy));
85141
}
86142

143+
/**
144+
@notice Emitted when the rewards module changes
145+
@param newFlywheelRewards the new rewards module
146+
*/
147+
event FlywheelRewardsUpdate(address indexed newFlywheelRewards);
148+
87149
/// @notice swap out the flywheel rewards contract
88150
function setFlywheelRewards(IFlywheelRewards newFlywheelRewards) external requiresAuth {
89151
flywheelRewards = newFlywheelRewards;
90152

91153
emit FlywheelRewardsUpdate(address(newFlywheelRewards));
92154
}
93155

156+
/**
157+
@notice Emitted when the booster module changes
158+
@param newBooster the new booster module
159+
*/
160+
event FlywheelBoosterUpdate(address indexed newBooster);
161+
94162
/// @notice swap out the flywheel booster contract
95163
function setBooster(IFlywheelBooster newBooster) external requiresAuth {
96164
flywheelBooster = newBooster;
97165

98166
emit FlywheelBoosterUpdate(address(newBooster));
99167
}
100168

101-
/// @notice accrue rewards for a single user on a strategy
102-
function accrue(ERC20 strategy, address user) public returns (uint256) {
103-
RewardsState memory state = strategyState[strategy];
104-
105-
if (state.index == 0) return 0;
169+
/*///////////////////////////////////////////////////////////////
170+
INTERNAL ACCOUNTING LOGIC
171+
//////////////////////////////////////////////////////////////*/
106172

107-
state = accrueStrategy(strategy, state);
108-
return accrueUser(strategy, user, state);
109-
}
110-
111-
/// @notice accrue rewards for two users on a strategy
112-
function accrue(ERC20 strategy, address user, address secondUser) public returns (uint256, uint256) {
113-
RewardsState memory state = strategyState[strategy];
114-
115-
if (state.index == 0) return (0, 0);
173+
struct RewardsState {
174+
/// @notice The strategy's last updated index
175+
uint224 index;
116176

117-
state = accrueStrategy(strategy, state);
118-
return (accrueUser(strategy, user, state), accrueUser(strategy, secondUser, state));
177+
/// @notice The timestamp the index was last updated at
178+
uint32 lastUpdatedTimestamp;
119179
}
180+
181+
/// @notice the fixed point factor of flywheel
182+
uint224 public constant ONE = 1e18;
120183

121-
/// @notice claim rewards for a given owner
122-
function claimRewards(address owner) external {
123-
uint256 accrued = rewardsAccrued[owner];
124-
125-
if (accrued != 0) {
126-
rewardsAccrued[owner] = 0;
184+
/// @notice The strategy index and last updated per strategy
185+
mapping(ERC20 => RewardsState) public strategyState;
127186

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

130-
emit ClaimRewards(owner, accrued);
131-
}
132-
}
133190

134191
/// @notice accumulate global rewards on a strategy
135192
function accrueStrategy(ERC20 strategy, RewardsState memory state) private returns(RewardsState memory rewardsState) {

src/interfaces/IFlywheelBooster.sol

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,31 @@ import {ERC20} from "solmate/tokens/ERC20.sol";
55

66
/**
77
@title Balance Booster Module for Flywheel
8-
@notice An optional module for virtually boosting user balances. This allows a Flywheel Core to plug into some balance boosting logic.
8+
@notice Flywheel is a general framework for managing token incentives.
9+
It takes reward streams to various *strategies* such as staking LP tokens and divides them among *users* of those strategies.
910
10-
Boosting logic can be associated with referrals, vote-escrow, or other strategies. It can even be used to model exotic strategies like borrowing.
11-
*/
11+
The Booster module is an optional module for virtually boosting or otherwise transforming user balances.
12+
If a booster is not configured, the strategies ERC-20 balanceOf/totalSupply will be used instead.
13+
14+
Boosting logic can be associated with referrals, vote-escrow, or other strategies.
15+
16+
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.
17+
This prevents gaming of the reward calculation function by using manipulated balances when accruing.
18+
*/
1219
interface IFlywheelBooster {
20+
21+
/**
22+
@notice calculate the boosted supply of a strategy.
23+
@param strategy the strategy to calculate boosted supply of
24+
@return the boosted supply
25+
*/
1326
function boostedTotalSupply(ERC20 strategy) external view returns(uint256);
1427

28+
/**
29+
@notice calculate the boosted balance of a user in a given strategy.
30+
@param strategy the strategy to calculate boosted balance of
31+
@param user the user to calculate boosted balance of
32+
@return the boosted balance
33+
*/
1534
function boostedBalanceOf(ERC20 strategy, address user) external view returns(uint256);
1635
}

src/interfaces/IFlywheelRewards.sol

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,35 @@ import {FlywheelCore} from "../FlywheelCore.sol";
66

77
/**
88
@title Rewards Module for Flywheel
9-
@notice The rewards module is a minimal interface for determining the quantity of rewards accrued to a flywheel strategy.
9+
@notice Flywheel is a general framework for managing token incentives.
10+
It takes reward streams to various *strategies* such as staking LP tokens and divides them among *users* of those strategies.
1011
11-
Different module strategies include:
12-
* a static reward rate per second
13-
* a decaying reward rate
14-
* a dynamic just-in-time reward stream
15-
* liquid governance reward delegation
12+
The Rewards module is responsible for:
13+
* determining the ongoing reward amounts to entire strategies (core handles the logic for dividing among users)
14+
* actually holding rewards that are yet to be claimed
15+
16+
The reward stream can follow arbitrary logic as long as the amount of rewards passed to flywheel core has been sent to this contract.
17+
18+
Different module strategies include:
19+
* a static reward rate per second
20+
* a decaying reward rate
21+
* a dynamic just-in-time reward stream
22+
* liquid governance reward delegation (Curve Gauge style)
23+
24+
SECURITY NOTE: The rewards strategy should be smooth and continuous, to prevent gaming the reward distribution by frontrunning.
1625
*/
1726
interface IFlywheelRewards {
27+
/**
28+
@notice calculate the rewards amount accrued to a strategy since the last update.
29+
@param strategy the strategy to accrue rewards for.
30+
@param lastUpdatedTimestamp the last time rewards were accrued for the strategy.
31+
@return rewards the amount of rewards accrued to the market
32+
*/
1833
function getAccruedRewards(ERC20 strategy, uint32 lastUpdatedTimestamp) external returns (uint256 rewards);
1934

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

38+
/// @notice return the reward token associated with flywheel core.
2239
function rewardToken() external view returns(ERC20);
2340
}

src/rewards/BaseFlywheelRewards.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {FlywheelCore} from "../FlywheelCore.sol";
88
/**
99
@title Flywheel Reward Module
1010
@notice Determines how many rewards accrue to each strategy globally over a given time period.
11+
@dev approves the flywheel core for the reward token to allow balances to be managed by the module but claimed from core.
1112
*/
1213
abstract contract BaseFlywheelRewards is IFlywheelRewards {
1314
using SafeTransferLib for ERC20;

0 commit comments

Comments
 (0)