Options Liquidity Mining (OLM)
Contract that implements an epoch-based liquidity mining reward system using oTokens
Introduction
Options Liquidity Mining allows the protocol to re-capture some of the value from liquidity mining rewards when LPs realize profit. Additionally, it can cap the amount of sell pressure on a protocols token in a down market by limiting the profitability of exercising option tokens to above the strike price.
The OLM contract implements a version of this using fixed strike call options. Protocols can deploy an OLM contract with their specific configuration of staked token and option parameters. The contract implements epoch-based staking rewards to issue new option tokens at fixed time intervals and at a new strike price based on the configuration when an epoch transitions. The stakedToken
and option payoutToken
are fixed when the OLM contract is deployed. The owner of the contract can update the other staking and option token parameters over time to adjust their rewards program. The owner can also optionally designate an Allowlist contract to limit which users can can stake in the contract. The allowlist should conform to the IAllowlist
interface.
Protocols can choose between two implementations of the OLM contract: Manual Strike or Oracle Strike.
Manual Strike: Owners must manually update the strike price to change it over time.
Oracle Strike: Strike price is automatically updated based on an oracle and discount. A minimum strike price can be set on the Oracle Strike version to prevent it from going too low.
Users can deposit the configured stakedToken
into the contract to earn rewards. Rewards are continuously accrued based on the configured reward rate and the total balance of staked tokens in the contract. Any user action updates the reward calculations. Additionally, user actions can trigger a new epoch start, which will earn them additional option tokens in the form of the epoch transition reward for paying the extra gas. Users can claim their outstanding rewards from all epochs or from the next unclaimed epoch. If the option token for a specific epoch has expired, the user will not receive any rewards for that period since they are now worthless.

Deployment
Setting up an OLM requires three steps.
Deploy an OLM contract from a factory.
Fund the OLM contract with
payoutTokens
to pay rewards with (via a simple ERC20 transfer). Owners must monitor the balance ofpayoutTokens
in the contract to ensure that users can claim their rewards.Initialize the OLM contract, including inputting the remaining options and staking parameters.
Step three of this process is accomplished by calling initialize
on this contract.
OLM Contract (abstract)
State Variables
stakedToken
Token that is staked in the OLM contract
ERC20 public immutable stakedToken;
depositsEnabled
Whether users can deposit staking tokens into the OLM contract at the current time
bool public depositsEnabled;
initialized
Whether the OLM contract has been initialized
No settings can be changed or tokens deposited before the OLM contract is initialized
bool public initialized;
allowlist
(Optional) Address of the allowlist contract which determines which addresses are allowed to interact with the OLM contract
IAllowlist public allowlist;
optionTeller
Option Teller contract that is used to deploy and create option tokens
IFixedStrikeOptionTeller public immutable optionTeller;
payoutToken
Token that stakers receive call options for
ERC20 public immutable payoutToken;
quoteToken
Token that stakers must pay to exercise the call options they receive
ERC20 public quoteToken;
timeUntilEligible
Amount of time (in seconds) from option token deployment to when it can be exercised
uint48 public timeUntilEligible;
eligibleDuration
Amount of time (in seconds) from when the option token is eligible to when it expires
uint48 public eligibleDuration;
receiver
Address that will receive the quote tokens when an option is exercised.
IMPORTANT: This address is the only one that can reclaim the payoutToken collateral for expired option tokens. Make sure this address can call reclaim
on the FixedStrikeOptionTeller contract for any option token address.
address public receiver;
epoch
Current staking epoch
uint48 public epoch;
epochDuration
Staking epoch duration
uint48 public epochDuration;
epochStart
Timestamp of the start of the current staking epoch
uint48 public epochStart;
REWARD_PERIOD
Amount of time (in seconds) that the reward rate is distributed over
uint48 public constant REWARD_PERIOD = uint48(1 days);
lastRewardUpdate
Timestamp when the stored rewards per token was last updated
uint48 public lastRewardUpdate;
rewardRate
Amount of option tokens rewarded per reward period
uint256 public rewardRate;
rewardsPerTokenStored
Global reward distribution variable, used to calculate user rewards
uint256 public rewardsPerTokenStored;
epochTransitionReward
Amount of option tokens that are rewarded for starting a new epoch
uint256 public epochTransitionReward;
epochRewardsPerTokenStart
Rewards Per Token value at the start of each epoch
mapping(uint48 => uint256) public epochRewardsPerTokenStart;
totalBalance
Total amount of staked tokens currently in the contract
uint256 public totalBalance;
stakeBalance
Mapping of staker address to their staked balance
mapping(address => uint256) public stakeBalance;
rewardsPerTokenClaimed
Mapping of staker address to the rewards per token they have claimed
mapping(address => uint256) public rewardsPerTokenClaimed;
lastEpochClaimed
Mapping of staker address to the last epoch they claimed rewards for
mapping(address => uint48) public lastEpochClaimed;
epochOptionTokens
Mapping of epochs to the option tokens that was rewarded for that epoch
mapping(uint48 => FixedStrikeOptionToken) public epochOptionTokens;
Modifiers
updateRewards
Modifier that updates the stored rewards per token before a function is executed
This modifier should be placed on any function where rewards are claimed or staked tokens are deposited/withdrawn.
Additionally, it should be placed on any functions that modify the reward parameters of the OLM contract.
modifier updateRewards();
tryNewEpoch
Modifier that tries to start a new epoch before a function is executed and rewards the caller for doing so
modifier tryNewEpoch();
requireInitialized
Modifier that requires the OLM contract to be initialized before a function is executed
modifier requireInitialized();
User Functions
stake
Deposit staking tokens into the contract to earn rewards
Only callable if deposits are enabled
Only callable if the user is allowed to stake per the allowlist
May receive reward if calling triggers new epoch
function stake(uint256 amount_, bytes calldata proof_)
external
nonReentrant
requireInitialized
updateRewards
tryNewEpoch;
Parameters
amount_
uint256
Amount of staking tokens to deposit
proof_
bytes
Optional proof data for specific allowlist implementations
unstake
Withdraw staking tokens from the contract
May receive reward if calling triggers new epoch
function unstake(uint256 amount_) external nonReentrant updateRewards tryNewEpoch;
Parameters
amount_
uint256
Amount of staking tokens to withdraw
unstakeAll
Withdraw entire balance of staking tokens from the contract
May receive reward if calling triggers new epoch
function unstakeAll() external nonReentrant updateRewards tryNewEpoch;
emergencyUnstakeAll
Withdraw entire balance of staking tokens without updating or claiming outstanding rewards.
Rewards will be lost if stake is withdrawn using this function. Only for emergency use.
function emergencyUnstakeAll() external nonReentrant;
claimRewards
Claim all outstanding rewards for the user across epochs
May receive reward if calling triggers new epoch
function claimRewards() external nonReentrant updateRewards tryNewEpoch returns (uint256);
claimNextEpochRewards
Claim all outstanding rewards for the user for the next unclaimed epoch (and any remaining rewards from the previously claimed epoch)
May receive reward if calling triggers new epoch
function claimNextEpochRewards() external nonReentrant updateRewards tryNewEpoch returns (uint256);
View Functions
currentRewardsPerToken
Returns the current rewards per token value updated to the second
function currentRewardsPerToken() public view returns (uint256);
nextStrikePrice
Returns the strike price that would be used if a new epoch started right now
function nextStrikePrice() public view virtual returns (uint256);
Owner Functions
initialize
Initializes the OLM contract
Only owner
This function can only be called once.
When the function completes, the contract is live. Users can start staking and claiming rewards.
function initialize(
ERC20 quoteToken_,
uint48 timeUntilEligible_,
uint48 eligibleDuration_,
address receiver_,
uint48 epochDuration_,
uint256 epochTransitionReward_,
uint256 rewardRate_,
IAllowlist allowlist_,
bytes calldata allowlistParams_,
bytes calldata other_
) external onlyOwner;
Parameters
quoteToken_
ERC20
Token that stakers must pay to exercise the call options they receive
timeUntilEligible_
uint48
Amount of time (in seconds) from option token deployment to when it can be exercised
eligibleDuration_
uint48
Amount of time (in seconds) from when the option token is eligible to when it expires
receiver_
address
Address that will receive the quote tokens when an option is exercised IMPORTANT: receiver is the only address that can retrieve payout token collateral from expired options. It must be able to call the reclaim
function on the Option Teller contract.
epochDuration_
uint48
Staking epoch duration (in seconds)
epochTransitionReward_
uint256
Amount of option tokens that are rewarded for starting a new epoch
rewardRate_
uint256
Amount of option tokens rewarded per reward period (1 day)
allowlist_
IAllowlist
Address of the allowlist contract that can be used to restrict who can stake in the OLM contract. If the zero address, then no allow list is used.
allowlistParams_
bytes
Parameters that are passed to the allowlist contract when this contract registers with it
other_
bytes
Additional parameters that are required by specific implementations of the OLM contract. Must be abi-encoded. See Manual Strike OLM or Oracle Strike OLM for details on what each expects here.
setDepositsEnabled
Toggle whether deposits are enabled
Only owner
function setDepositsEnabled(bool depositsEnabled_) external onlyOwner requireInitialized;
Parameters
depositsEnabled_
bool
Whether deposits should be enabled
triggerNextEpoch
Manually start a new epoch
Only owner
function triggerNextEpoch() external onlyOwner requireInitialized updateRewards;
withdrawPayoutTokens
Withdraw payout tokens that were deposited to the contract for rewards
Only owner
function withdrawPayoutTokens(address to_, uint256 amount_) external onlyOwner;
Parameters
to_
address
The address to withdraw to
amount_
uint256
The amount to withdraw
setRewardRate
Set the staking reward rate
Only owner
function setRewardRate(uint256 rewardRate_) external onlyOwner requireInitialized updateRewards;
Parameters
rewardRate_
uint256
Amount of option tokens rewarded per reward period (1 day)
setEpochDuration
Set the epoch duration
Only owner
function setEpochDuration(uint48 epochDuration_) external onlyOwner requireInitialized;
Parameters
epochDuration_
uint48
Staking epoch duration (in seconds)
setEpochTransitionReward
Set the epoch transition reward
Only owner
function setEpochTransitionReward(uint256 amount_) external onlyOwner requireInitialized;
Parameters
amount_
uint256
Amount of option tokens that are rewarded for starting a new epoch
setOptionReceiver
Set the option receiver
Only owner
function setOptionReceiver(address receiver_) external onlyOwner requireInitialized;
Parameters
receiver_
address
Address that will receive the quote tokens when an option is exercised IMPORTANT: receiver is the only address that can retrieve payout token collateral from expired options. It must be able to call the reclaim
function on the Option Teller contract.
setOptionDuration
Set the option duration
Only owner
function setOptionDuration(uint48 timeUntilEligible_, uint48 eligibleDuration_) external onlyOwner requireInitialized;
Parameters
timeUntilEligible_
uint48
Amount of time (in seconds) from option token deployment to when it can be exercised
eligibleDuration_
uint48
Amount of time (in seconds) from when the option token is eligible to when it expire
setQuoteToken
Set the quote token that is used for the option tokens
Only owner
function setQuoteToken(ERC20 quoteToken_) external virtual onlyOwner requireInitialized;
Parameters
quoteToken_
ERC20
Token that stakers must pay to exercise the call options they receive
setAllowlist
function setAllowlist(IAllowlist allowlist_, bytes calldata allowlistParams_) external onlyOwner requireInitialized;
Events
NewEpoch
event NewEpoch(uint48 indexed epoch_, FixedStrikeOptionToken optionToken_);
Errors
OLM_InvalidParams
error OLM_InvalidParams();
OLM_InvalidAmount
error OLM_InvalidAmount();
OLM_InvalidEpoch
error OLM_InvalidEpoch();
OLM_ZeroBalance
error OLM_ZeroBalance();
OLM_PreviousUnclaimedEpoch
error OLM_PreviousUnclaimedEpoch();
OLM_AlreadyInitialized
error OLM_AlreadyInitialized();
OLM_NotInitialized
error OLM_NotInitialized();
OLM_DepositsDisabled
error OLM_DepositsDisabled();
OLM_NotAllowed
error OLM_NotAllowed();