Skip to content

Commit

Permalink
Merge pull request #529 from fei-protocol/feat/metagov-angle
Browse files Browse the repository at this point in the history
FIP-78a: Metagovernance - ANGLE
  • Loading branch information
eswak committed Mar 7, 2022
2 parents faefef0 + b636a2e commit 6f211da
Show file tree
Hide file tree
Showing 25 changed files with 1,657 additions and 34 deletions.
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);

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;
}
}

0 comments on commit 6f211da

Please sign in to comment.