Comment on page
Angle Distributor - Distributing ANGLE Inflation
Distributing rewards across all staking contracts (or gauges) of the protocol
AngleDistributorcontract 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
veANGLEholders for this gauge in the
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
AngleDistributorcontract 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
AngleDistributoris 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
This contract is an adapted fork of two contracts developed by Curve and FRAX:
ReentrancyGuardUpgradeable. The contract is upgradeable and can be upgraded only by the governor.
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
BASE: Base used for computation
controller: Address of the
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
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
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.
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
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
distributeRewardis called, the mapping
lastTimeGaugePaidfor 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
_updateMiningParametersfunction that changes the ANGLE emission rate every week.
It is at the level of the
distributeRewardfunction 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
AngleDistributorsends rewards to these gauges by using the
- Type 1 gauges: This corresponded to the different
PerpetualManagerstaking contracts, for which the
notifyRewardAmountinterface 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
delegateGaugewhich receives ANGLE rewards for all of them and then distributes these rewards to the different gauges. This
delegateGaugeaddress should be a multisig or a trusted contract
- Type >2 gauges: Inspired from Frax interfaces, some smart contracts with a
pullAndBridgeinterface 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.
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
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
distributeRewardfunction, the start of each mining epoch is not rounded to the nearest Thursday Midningt UTC. This means that if the
startEpochTimeis 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.
tokenAddress: Address of the ERC20 token to withdraw
to: Address to transfer to
amount: 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
distributeRewardhas 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
distributeRewardcalls for all existing gauges. As this function assumes that
distributeRewardhas been called during the week, it also assumes that the
startEpochSupplyparameter 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
GaugeControllercontract, killing of gauges takes place in the
AngleDistributorcontract. 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.