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.

  1. Deploy an OLM contract from a factory.

  2. Fund the OLM contract with payoutTokens to pay rewards with (via a simple ERC20 transfer). Owners must monitor the balance of payoutTokens in the contract to ensure that users can claim their rewards.

  3. 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)

Git Source

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

NameTypeDescription

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

NameTypeDescription

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

NameTypeDescription

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

NameTypeDescription

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

NameTypeDescription

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

NameTypeDescription

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

NameTypeDescription

epochDuration_

uint48

Staking epoch duration (in seconds)

setEpochTransitionReward

Set the epoch transition reward

Only owner

function setEpochTransitionReward(uint256 amount_) external onlyOwner requireInitialized;

Parameters

NameTypeDescription

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

NameTypeDescription

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

NameTypeDescription

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

NameTypeDescription

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();