Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #529 from fei-protocol/feat/metagov-angle
FIP-78a: Metagovernance - ANGLE
- Loading branch information
Showing
25 changed files
with
1,657 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import "./SnapshotDelegatorPCVDeposit.sol"; | ||
import "./utils/VoteEscrowTokenManager.sol"; | ||
import "./utils/LiquidityGaugeManager.sol"; | ||
import "./utils/OZGovernorVoter.sol"; | ||
|
||
/// @title ANGLE Token PCV Deposit | ||
/// @author Fei Protocol | ||
contract AngleDelegatorPCVDeposit is | ||
SnapshotDelegatorPCVDeposit, | ||
VoteEscrowTokenManager, | ||
LiquidityGaugeManager, | ||
OZGovernorVoter | ||
{ | ||
address private constant ANGLE_TOKEN = | ||
0x31429d1856aD1377A8A0079410B297e1a9e214c2; | ||
address private constant ANGLE_VE_TOKEN = | ||
0x0C462Dbb9EC8cD1630f1728B2CFD2769d09f0dd5; | ||
address private constant ANGLE_GAUGE_MANAGER = | ||
0x9aD7e7b0877582E14c17702EecF49018DD6f2367; | ||
bytes32 private constant ANGLE_SNAPSHOT_SPACE = | ||
keccak256("anglegovernance.eth"); | ||
|
||
/// @notice ANGLE token manager | ||
/// @param _core Fei Core for reference | ||
/// @param _initialDelegate initial delegate for snapshot votes | ||
constructor(address _core, address _initialDelegate) | ||
SnapshotDelegatorPCVDeposit( | ||
_core, | ||
IERC20(ANGLE_TOKEN), // token used in reporting | ||
ANGLE_SNAPSHOT_SPACE, // snapshot spaceId | ||
_initialDelegate | ||
) | ||
VoteEscrowTokenManager( | ||
IERC20(ANGLE_TOKEN), // liquid token | ||
IVeToken(ANGLE_VE_TOKEN), // vote-escrowed token | ||
4 * 365 * 86400 // vote-escrow time = 4 years | ||
) | ||
LiquidityGaugeManager(ANGLE_GAUGE_MANAGER) | ||
OZGovernorVoter() | ||
{} | ||
|
||
/// @notice returns total balance of PCV in the Deposit | ||
function balance() public view override returns (uint256) { | ||
return _totalTokensManaged(); // liquid and vote-escrowed tokens | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import "../../refs/CoreRef.sol"; | ||
import "../../core/TribeRoles.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
interface ILiquidityGauge { | ||
function deposit(uint256 value) external; | ||
|
||
function withdraw(uint256 value, bool claim_rewards) external; | ||
|
||
function claim_rewards() external; | ||
|
||
function balanceOf(address) external view returns (uint256); | ||
|
||
function staking_token() external view returns (address); | ||
|
||
function reward_tokens(uint256 i) external view returns (address token); | ||
|
||
function reward_count() external view returns (uint256 nTokens); | ||
} | ||
|
||
interface ILiquidityGaugeController { | ||
function vote_for_gauge_weights(address gauge_addr, uint256 user_weight) | ||
external; | ||
|
||
function last_user_vote(address user, address gauge) | ||
external | ||
view | ||
returns (uint256); | ||
|
||
function vote_user_power(address user) external view returns (uint256); | ||
|
||
function gauge_types(address gauge) external view returns (int128); | ||
} | ||
|
||
/// @title Liquidity gauge manager, used to stake tokens in liquidity gauges. | ||
/// @author Fei Protocol | ||
abstract contract LiquidityGaugeManager is CoreRef { | ||
// Events | ||
event GaugeControllerChanged( | ||
address indexed oldController, | ||
address indexed newController | ||
); | ||
event GaugeSetForToken(address indexed token, address indexed gauge); | ||
event GaugeVote(address indexed gauge, uint256 amount); | ||
event GaugeStake(address indexed gauge, uint256 amount); | ||
event GaugeUnstake(address indexed gauge, uint256 amount); | ||
event GaugeRewardsClaimed( | ||
address indexed gauge, | ||
address indexed token, | ||
uint256 amount | ||
); | ||
|
||
/// @notice address of the gauge controller used for voting | ||
address public gaugeController; | ||
|
||
/// @notice mapping of token staked to gauge address | ||
mapping(address => address) public tokenToGauge; | ||
|
||
constructor(address _gaugeController) { | ||
gaugeController = _gaugeController; | ||
} | ||
|
||
/// @notice Set the gauge controller used for gauge weight voting | ||
/// @param _gaugeController the gauge controller address | ||
function setGaugeController(address _gaugeController) | ||
public | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_GAUGE_ADMIN) | ||
{ | ||
require( | ||
gaugeController != _gaugeController, | ||
"LiquidityGaugeManager: same controller" | ||
); | ||
|
||
address oldController = gaugeController; | ||
gaugeController = _gaugeController; | ||
|
||
emit GaugeControllerChanged(oldController, gaugeController); | ||
} | ||
|
||
/// @notice Set gauge for a given token. | ||
/// @param token the token address to stake in gauge | ||
/// @param gaugeAddress the address of the gauge where to stake token | ||
function setTokenToGauge(address token, address gaugeAddress) | ||
public | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_GAUGE_ADMIN) | ||
{ | ||
require( | ||
ILiquidityGauge(gaugeAddress).staking_token() == token, | ||
"LiquidityGaugeManager: wrong gauge for token" | ||
); | ||
require( | ||
ILiquidityGaugeController(gaugeController).gauge_types( | ||
gaugeAddress | ||
) >= 0, | ||
"LiquidityGaugeManager: wrong gauge address" | ||
); | ||
tokenToGauge[token] = gaugeAddress; | ||
|
||
emit GaugeSetForToken(token, gaugeAddress); | ||
} | ||
|
||
/// @notice Vote for a gauge's weight | ||
/// @param token the address of the token to vote for | ||
/// @param gaugeWeight the weight of gaugeAddress in basis points [0, 10_000] | ||
function voteForGaugeWeight(address token, uint256 gaugeWeight) | ||
public | ||
whenNotPaused | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_VOTE_ADMIN) | ||
{ | ||
address gaugeAddress = tokenToGauge[token]; | ||
require( | ||
gaugeAddress != address(0), | ||
"LiquidityGaugeManager: token has no gauge configured" | ||
); | ||
ILiquidityGaugeController(gaugeController).vote_for_gauge_weights( | ||
gaugeAddress, | ||
gaugeWeight | ||
); | ||
|
||
emit GaugeVote(gaugeAddress, gaugeWeight); | ||
} | ||
|
||
/// @notice Stake tokens in a gauge | ||
/// @param token the address of the token to stake in the gauge | ||
/// @param amount the amount of tokens to stake in the gauge | ||
function stakeInGauge(address token, uint256 amount) | ||
public | ||
whenNotPaused | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_GAUGE_ADMIN) | ||
{ | ||
address gaugeAddress = tokenToGauge[token]; | ||
require( | ||
gaugeAddress != address(0), | ||
"LiquidityGaugeManager: token has no gauge configured" | ||
); | ||
IERC20(token).approve(gaugeAddress, amount); | ||
ILiquidityGauge(gaugeAddress).deposit(amount); | ||
|
||
emit GaugeStake(gaugeAddress, amount); | ||
} | ||
|
||
/// @notice Stake all tokens held in a gauge | ||
/// @param token the address of the token to stake in the gauge | ||
function stakeAllInGauge(address token) | ||
public | ||
whenNotPaused | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_GAUGE_ADMIN) | ||
{ | ||
address gaugeAddress = tokenToGauge[token]; | ||
require( | ||
gaugeAddress != address(0), | ||
"LiquidityGaugeManager: token has no gauge configured" | ||
); | ||
uint256 amount = IERC20(token).balanceOf(address(this)); | ||
IERC20(token).approve(gaugeAddress, amount); | ||
ILiquidityGauge(gaugeAddress).deposit(amount); | ||
|
||
emit GaugeStake(gaugeAddress, amount); | ||
} | ||
|
||
/// @notice Unstake tokens from a gauge | ||
/// @param token the address of the token to unstake from the gauge | ||
/// @param amount the amount of tokens to unstake from the gauge | ||
function unstakeFromGauge(address token, uint256 amount) | ||
public | ||
whenNotPaused | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_GAUGE_ADMIN) | ||
{ | ||
address gaugeAddress = tokenToGauge[token]; | ||
require( | ||
gaugeAddress != address(0), | ||
"LiquidityGaugeManager: token has no gauge configured" | ||
); | ||
ILiquidityGauge(gaugeAddress).withdraw(amount, false); | ||
|
||
emit GaugeUnstake(gaugeAddress, amount); | ||
} | ||
|
||
/// @notice Claim rewards associated to a gauge where this contract stakes | ||
/// tokens. | ||
function claimGaugeRewards(address gaugeAddress) public whenNotPaused { | ||
uint256 nTokens = ILiquidityGauge(gaugeAddress).reward_count(); | ||
address[] memory tokens = new address[](nTokens); | ||
uint256[] memory amounts = new uint256[](nTokens); | ||
|
||
for (uint256 i = 0; i < nTokens; i++) { | ||
tokens[i] = ILiquidityGauge(gaugeAddress).reward_tokens(i); | ||
amounts[i] = IERC20(tokens[i]).balanceOf(address(this)); | ||
} | ||
|
||
ILiquidityGauge(gaugeAddress).claim_rewards(); | ||
|
||
for (uint256 i = 0; i < nTokens; i++) { | ||
amounts[i] = | ||
IERC20(tokens[i]).balanceOf(address(this)) - | ||
amounts[i]; | ||
|
||
emit GaugeRewardsClaimed(gaugeAddress, tokens[i], amounts[i]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import "../../refs/CoreRef.sol"; | ||
import "../../core/TribeRoles.sol"; | ||
|
||
interface IOZGovernor { | ||
function propose( | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
string memory description | ||
) external returns (uint256 proposalId); | ||
|
||
function castVote(uint256 proposalId, uint8 support) | ||
external | ||
returns (uint256 weight); | ||
|
||
function state(uint256 proposalId) external view returns (uint256); | ||
} | ||
|
||
/// @title Abstract class to interact with an OZ governor. | ||
/// @author Fei Protocol | ||
abstract contract OZGovernorVoter is CoreRef { | ||
// Events | ||
event Proposed(IOZGovernor indexed governor, uint256 proposalId); | ||
event Voted( | ||
IOZGovernor indexed governor, | ||
uint256 proposalId, | ||
uint256 weight, | ||
uint8 support | ||
); | ||
|
||
/// @notice propose a new proposal on the target governor. | ||
function propose( | ||
IOZGovernor governor, | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
string memory description | ||
) | ||
external | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_VOTE_ADMIN) | ||
returns (uint256) | ||
{ | ||
uint256 proposalId = governor.propose( | ||
targets, | ||
values, | ||
calldatas, | ||
description | ||
); | ||
emit Proposed(governor, proposalId); | ||
return proposalId; | ||
} | ||
|
||
/// @notice cast a vote on a given proposal on the target governor. | ||
function castVote( | ||
IOZGovernor governor, | ||
uint256 proposalId, | ||
uint8 support | ||
) | ||
external | ||
onlyTribeRole(TribeRoles.METAGOVERNANCE_VOTE_ADMIN) | ||
returns (uint256) | ||
{ | ||
uint256 weight = governor.castVote(proposalId, support); | ||
emit Voted(governor, proposalId, weight, support); | ||
return weight; | ||
} | ||
} |
Oops, something went wrong.