Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIP-78a: Metagovernance - ANGLE #529

Merged
merged 49 commits into from Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
756b022
UniswapPCVDeposit: MINTER_ROLE is now optional
eswak Feb 8, 2022
be65493
Move SnapshotDelegatorPCVDeposit to metagov folder
eswak Feb 8, 2022
21451a6
Add ANGLE metagovernance smartcontracts
eswak Feb 8, 2022
aaf5a22
Add FIP-78a DAO script
eswak Feb 8, 2022
7677fd6
Use TribeRoles for SnapshotDelegatorPCVDeposit
eswak Feb 8, 2022
045e7ef
Use new TribeRoles ACL for Angle metagovernance
eswak Feb 9, 2022
ec5dacc
Add additional checks on gauge whitelisting
eswak Feb 9, 2022
99604ca
Add GaugeLens and UniswapLens to read agEUR balance
eswak Feb 9, 2022
81175e0
Add e2e tests for metagov features
eswak Feb 14, 2022
0be1fa3
FIP-78a: Increase amount of pre-minted FEI
eswak Feb 14, 2022
d5585ed
Add FIP-78a proposal description
eswak Feb 14, 2022
876da8d
Merge branch 'develop' into feat/metagov-angle
eswak Feb 14, 2022
5471997
Remove passed proposals from proposal config
eswak Feb 14, 2022
27c0339
Add DEBUG type for proposal step-by-step execution
eswak Feb 15, 2022
6c6b437
Fix FIP-78a description
eswak Feb 15, 2022
1c1c6ed
Remove .only
eswak Feb 15, 2022
3b3c450
Fix dependencies
eswak Feb 15, 2022
4de1b16
Fix FIP-78a e2e tests and on-chain description
eswak Feb 15, 2022
15db3f0
Remove deprecated AngleUniswapPCVDeposit from CR oracle
eswak Feb 15, 2022
c7ed226
Add agEurUniswapPCVDeposit to CR oracle config
eswak Feb 15, 2022
d6038dc
Define constants in AngleDelegatorPCVDeposit
eswak Feb 16, 2022
3f7d0f8
Add VoteEscrowTokenManager.setLockDuration() fn
eswak Feb 16, 2022
92dd8a4
Add natspec in GaugeLens.sol
eswak Feb 16, 2022
3694a5e
Update GaugeLens to support FEI gauges
eswak Feb 16, 2022
44d4338
Remove _feiIsToken0 from UniswapLens constructor
eswak Feb 16, 2022
0e7baf7
Split metagov e2e tests in 3 files (1 for each contract)
eswak Feb 16, 2022
8a7d28c
Remove DEBUG category of proposals
eswak Feb 16, 2022
75e93a3
Add events in metagov utils contracts
eswak Feb 16, 2022
285bcaf
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Feb 16, 2022
b7cd7ec
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Feb 16, 2022
bf047e7
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Feb 24, 2022
939615b
Joey review 1
eswak Feb 24, 2022
85bec40
Joey review 2
eswak Feb 24, 2022
e616f8e
Use more realistic slippage tolerance for agEUR deposit
eswak Feb 24, 2022
9fc6b09
Update expected agEUR deposit resistantBalanceAndFei.fei
eswak Feb 24, 2022
14333ff
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Feb 25, 2022
7c45d24
Remove METAGOVERNANCE_GAUGE_STAKING role
eswak Feb 28, 2022
cb2b930
Deploy FIP-78a contracts
eswak Feb 28, 2022
b55ac8f
Remove FIP_84a from proposals_config
eswak Feb 28, 2022
8afb345
Remove fip_84b and fip_84c from proposals config
eswak Feb 28, 2022
fa6a8e3
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Feb 28, 2022
b13f2f3
Skip e2e-migrate-proxies
eswak Feb 28, 2022
931feb9
Update dependencies files
eswak Feb 28, 2022
1bf9e98
npm run prettier:sol
eswak Feb 28, 2022
145f184
add fip_78a proposalId
eswak Feb 28, 2022
cfdb449
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Feb 28, 2022
969393a
Remove FIP-78a from proposals_config
eswak Mar 7, 2022
c59c9e4
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Mar 7, 2022
b636a2e
Merge remote-tracking branch 'origin/develop' into feat/metagov-angle
eswak Mar 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions contracts/core/TribeRoles.sol
Expand Up @@ -59,6 +59,21 @@ library TribeRoles {
/// @notice manages the constituents of Optimistic Timelocks, including Proposers and Executors
bytes32 internal constant OPTIMISTIC_ADMIN = keccak256("OPTIMISTIC_ADMIN");

/// @notice manages meta-governance actions, like voting & delegating.
/// Also used to vote for gauge weights & similar liquid governance things.
bytes32 internal constant METAGOVERNANCE_VOTE_ADMIN =
keccak256("METAGOVERNANCE_VOTE_ADMIN");

/// @notice allows to manage locking of vote-escrowed tokens, and staking/unstaking
/// governance tokens from a pre-defined contract in order to eventually allow voting.
/// Examples: ANGLE <> veANGLE, AAVE <> stkAAVE, CVX <> vlCVX, CRV > cvxCRV.
bytes32 internal constant METAGOVERNANCE_TOKEN_STAKING =
keccak256("METAGOVERNANCE_TOKEN_STAKING");

/// @notice manages whitelisting of gauges where the protocol's tokens can be staked
bytes32 internal constant METAGOVERNANCE_GAUGE_ADMIN =
keccak256("METAGOVERNANCE_GAUGE_ADMIN");

/*///////////////////////////////////////////////////////////////
Minor Roles
//////////////////////////////////////////////////////////////*/
Expand Down
49 changes: 49 additions & 0 deletions contracts/metagov/AngleDelegatorPCVDeposit.sol
@@ -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
}
}
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import "../PCVDeposit.sol";
import "../core/TribeRoles.sol";
import "../pcv/PCVDeposit.sol";

interface DelegateRegistry {
function setDelegate(bytes32 id, address delegate) external;
Expand Down Expand Up @@ -65,7 +66,7 @@ contract SnapshotDelegatorPCVDeposit is PCVDeposit {
function deposit() external override {}

/// @notice returns total balance of PCV in the Deposit
function balance() public view override returns (uint256) {
function balance() public view virtual override returns (uint256) {
return token.balanceOf(address(this));
}

Expand All @@ -74,15 +75,27 @@ contract SnapshotDelegatorPCVDeposit is PCVDeposit {
return address(token);
}

/// @notice sets the snapshot space ID
function setSpaceId(bytes32 _spaceId)
external
onlyTribeRole(TribeRoles.METAGOVERNANCE_VOTE_ADMIN)
{
spaceId = _spaceId;
}

/// @notice sets the snapshot delegate
/// @dev callable by governor or admin
function setDelegate(address newDelegate) external onlyGovernorOrAdmin {
function setDelegate(address newDelegate)
external
onlyTribeRole(TribeRoles.METAGOVERNANCE_VOTE_ADMIN)
{
_delegate(newDelegate);
}

/// @notice clears the delegate from snapshot
/// @dev callable by governor or guardian
function clearDelegate() external onlyGuardianOrGovernor {
function clearDelegate()
external
onlyTribeRole(TribeRoles.METAGOVERNANCE_VOTE_ADMIN)
{
address oldDelegate = delegate;
DELEGATE_REGISTRY.clearDelegate(spaceId);

Expand Down
204 changes: 204 additions & 0 deletions contracts/metagov/utils/LiquidityGaugeManager.sol
@@ -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);
eswak marked this conversation as resolved.
Show resolved Hide resolved

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]);
}
}
}
70 changes: 70 additions & 0 deletions contracts/metagov/utils/OZGovernorVoter.sol
@@ -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;
}
}