Comment on page
👐
Angle Distributor - Distributing ANGLE Inflation
Distributing rewards across all staking contracts (or gauges) of the protocol
- Contract Name:
AngleDistributor.sol
- ABI:
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
functionThis contract is an adapted fork of two contracts developed by Curve and FRAX:
Implements
AngleDistributorEvents
, AccessControlUpgradeable
, and ReentrancyGuardUpgradeable
. The contract is upgradeable and can be upgraded only by the governor.WEEK
: Length of a weekRATE_REDUCTION_TIME
: Time between two reductions of the emission rate: it is set to a weekRATE_REDUCTION_COEFFICIENT
: Decrease of the rate at eachRATE_REDUCTION_TIME
. It is currently set toBASE
: Base used for computation
controller
: Address of theGaugeController
contractrewardToken
: 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 tokendelegateGauge
: Address responsible for pulling rewards of type 2 gauges and distributing it to the associated contracts
rate
: ANGLE current emission rate, it is first defined in the initializer and then updated every weekstartEpochTime
: Timestamp at which the current emission epoch startedstartEpochSupply
: 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 itminingEpoch
: 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 variabledistributionsOn
: Whether ANGLE distribution through this contract is on or no
lastTimeGaugePaid
: Maps the address of a gauge to the last time this gauge received rewardskilledGauges
: Maps the address of a gauge to whether it was killed or not. A gauge killed in this contract cannot receive any rewards.
GUARDIAN_ROLE
: Used to activate or deactivate rapidly distribution with this contractGOVERNOR_ROLE
: Since this contract controls a large amount of governance tokens, there is a need for a governor
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. TheAngleDistributor
sends rewards to these gauges by using thedeposit_reward_token
interface - Type 1 gauges: This corresponded to the different
PerpetualManager
staking contracts, for which thenotifyRewardAmount
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. ThisdelegateGauge
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 1rewardTally
: Amount of tokens sent to the gauge
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.
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.
The following functions can only be called by the governor.
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 withdrawto
: Address to transfer toamount
: Amount to transfer
function setGaugeController(address _controller) external;
Sets a new gauge controller. This function should never be used in practice.
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
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.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.
The following functions can only be called by the guardian (and by governance which also has the guardian role)
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.