Search
K
Comment on page
👐

Angle Distributor - Distributing ANGLE Inflation

Distributing rewards across all staking contracts (or gauges) of the protocol

1. Introduction

The AngleDistributor contract controls and handles the distribution of governance tokens to the different staking contracts (also called gauges) of the protocol. It is responsible for sending rewards to the gauges that have been added to the GaugeController. The amount of rewards sent to a given gauge is determined based on the votes of veANGLE holders for this gauge in the GaugeController contract.
This contract handles the ANGLE inflation and emissions: it is there that the emission rate is set and that the rate reduction coefficient between two weeks is defined. The AngleDistributor contract will therefore be holding all the ANGLE tokens to distribute via liquidity mining.
It has public methods exposed for keepers to distribute rewards across the different gauges. The AngleDistributor is able to distinguish the different types of gauges and treats each type accordingly. There is one way of routing ANGLE rewards per gauge type, check for more details the description of the distributeReward function
This contract is an adapted fork of two contracts developed by Curve and FRAX:

2. Contract Details

Interfaces

Implements AngleDistributorEvents, AccessControlUpgradeable, and ReentrancyGuardUpgradeable. The contract is upgradeable and can be upgraded only by the governor.

Parameters

Constants

  • WEEK: Length of a week
  • RATE_REDUCTION_TIME: Time between two reductions of the emission rate: it is set to a week
  • RATE_REDUCTION_COEFFICIENT: Decrease of the rate at each RATE_REDUCTION_TIME. It is currently set to
    1.5(152)×10181.5 ^{(\frac{1}{52})} \times 10^{18}
  • BASE: Base used for computation

References

  • controller: Address of the GaugeController contract
  • rewardToken: Address of the token given as a reward. This contract could technically work for any reward token. In the case of the Angle Protocol, it will obviously be the ANGLE token
  • delegateGauge: Address responsible for pulling rewards of type 2 gauges and distributing it to the associated contracts

Parameters

  • rate: ANGLE current emission rate, it is first defined in the initializer and then updated every week
  • startEpochTime: Timestamp at which the current emission epoch started
  • startEpochSupply: Amount of ANGLE tokens distributed through staking at the start of the epoch. This is an informational variable used to track how much has been distributed through liquidity mining. It was kept as Curve and Frax had it
  • miningEpoch: Index of the current emission epoch. Here also, this variable is not useful per se inside the smart contracts of the protocol, it is just an informational variable
  • distributionsOn: Whether ANGLE distribution through this contract is on or no

Mappings

  • lastTimeGaugePaid: Maps the address of a gauge to the last time this gauge received rewards
  • killedGauges: Maps the address of a gauge to whether it was killed or not. A gauge killed in this contract cannot receive any rewards.

Access Control

  • GUARDIAN_ROLE: Used to activate or deactivate rapidly distribution with this contract
  • GOVERNOR_ROLE: Since this contract controls a large amount of governance tokens, there is a need for a governor

3. Key Mechanisms & Concepts

External Functions for Keepers

distributeReward

function distributeReward(address gaugeAddr) external nonReentrant returns (uint256, uint256);
Distributes rewards to a given gauge. This function is permissionless meaning anyone can call it to send ANGLE rewards to a gauge. If this function is called for the same gauge with less than a week since the last call it will not do anything.
More precisely, each time distributeReward is called, the mapping lastTimeGaugePaid for this gauge is updated to the date of the last Thursday Midnight UTC preceding. So if you call this function on a Saturday, you'll have to wait till the following Thursday at midnight (1 minute after Wednesday 23h59) to distribute rewards again to this gauge.
This function also ensures that the emission rate has been correctly updated as it calls the _updateMiningParameters function that changes the ANGLE emission rate every week.
It is at the level of the distributeReward function that the different types of gauges are handled differently. Rewards are not sent to type 0 gauge as they are sent to type 1 gauges for instance. In summary:
  • Type 0 gauges: This corresponds to mainnet LiquidityV4Gauge contracts. The AngleDistributor sends rewards to these gauges by using the deposit_reward_token interface
  • Type 1 gauges: This corresponded to the different PerpetualManager staking contracts, for which the notifyRewardAmount interface of the gauge contracts was used. There are currently no active gauges of type 1.
  • Type 2 gauges: Rewards are simply sent to such gauges. Usually type 2 gauges are external staking contracts of the protocol for which interfaces can change. Some other chains gauges also have this type. It is possible to set for all type 2 gauges a delegateGauge which receives ANGLE rewards for all of them and then distributes these rewards to the different gauges. This delegateGauge address should be a multisig or a trusted contract
  • Type >2 gauges: Inspired from Frax interfaces, some smart contracts with a pullAndBridge interface could be used to automatically bridge the tokens. There is no such gauge at the moment.
Note that no rewards are given for calling this function, and so keeper would be doing this at a loss. In the future, wrapper contracts distributing rewards could handle this.
Return Values:
  • weeksElapsed: Number of weeks elapsed since the last time rewards were distributed to this gauge. If the system is correctly maintained, this number of weeks should be no more than 1
  • rewardTally: Amount of tokens sent to the gauge

distributeRewardToMultipleGauges

function distributeRewardToMultipleGauges(address[] memory gauges) external nonReentrant;
This function performs the same operations as the function above but can do it for multiple gauges at the same time. It was introduced to send some gas costs for keepers distributing rewards.

updateMiningParameters

function updateMiningParameters() external;
Updates mining rate and supply at the start of each mining epoch (every week). Note that contrarily to what is done in the distributeReward function, the start of each mining epoch is not rounded to the nearest Thursday Midningt UTC. This means that if the startEpochTime is set on a Saturday, every Saturday, it'll be possible to call this function.
This does not have any implication on the amount of rewards distributed to gauges since the rate is constant within a week.

Governor Functions

The following functions can only be called by the governor.

recoverERC20

function recoverERC20(address tokenAddress, address to, uint256 amount) external;
Withdraws ERC20 tokens that could accrue on this contract. This function could be used to withdraw ANGLE tokens but it verifies that the withdrawal leaves enough tokens for what's planned by the liquidity mining contract.
Parameters:
  • tokenAddress: Address of the ERC20 token to withdraw
  • to: Address to transfer to
  • amount: Amount to transfer

setGaugeController

function setGaugeController(address _controller) external;
Sets a new gauge controller. This function should never be used in practice.

setDelegateGauge

function setDelegateGauge(address _delegateGauge) external;
Sets a new delegate gauge for pulling rewards of type 2 gauges. This function can be used to remove or introduce the pulling of rewards to a given address

setRate

function setRate(uint256 _newRate) external;
This function can be used by governance to change the ANGLE emission rate. It is important to be super wary when calling this function and to make sure that distributeReward has been called for all gauges in the past weeks. If not, gauges may get an incorrect distribution of ANGLE rewards for these past weeks based on the new rate and not on the old rate.
Governance should thus make sure to call this function rarely and when it does to do it after the weekly distributeReward calls for all existing gauges. As this function assumes that distributeReward has been called during the week, it also assumes that the startEpochSupply parameter has been put up to date.

toggleGauge

function toggleGauge(uint256 _newRate) external;
Toggles the status of a gauge to either killed or unkilled. As it is impossible to kill a gauge in the GaugeController contract, killing of gauges takes place in the AngleDistributor contract. This means that people could vote for a gauge in the gauge controller contract but that rewards are not going to be distributed to it in the end: people would need to remove their weights on the gauge killed to end the diminution in rewards.
In the case of a gauge being killed, this function resets the timestamps at which this gauge has been approved and disapproves the gauge to spend the token. It should be cautiously called by governance as it could result in less ANGLE overall rewards than initially planned if people do not remove their voting weights to the killed gauge.

Guardian Function

The following functions can only be called by the guardian (and by governance which also has the guardian role)

toggleDistributions

function toggleDistributions() external;
Halts or activates distribution of rewards to different gauges. This function should only be called one in the first week to activate rewards.