Skip to content

Commit

Permalink
Merge branch 'develop' into feat/pcvguardian-v3
Browse files Browse the repository at this point in the history
  • Loading branch information
eswak authored Jul 7, 2022
2 parents ffaddfd + 762c3f0 commit f030292
Show file tree
Hide file tree
Showing 49 changed files with 2,003 additions and 1,131 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
e2e-test:
working_directory: ~/repo
executor: nodeimage
parallelism: 19
parallelism: 17
steps:
- attach_workspace:
at: ./
Expand Down
2 changes: 1 addition & 1 deletion block.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
15039959
15089978
71 changes: 71 additions & 0 deletions contracts/metagov/DelegatorPCVDeposit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import "../core/TribeRoles.sol";
import "../pcv/PCVDeposit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";

/// @title Delegator PCV Deposit
/// This contract simply holds an ERC20 token, and delegate its voting power
/// to an address. The ERC20 token needs to implement a delegate(address) method.
/// @author eswak
contract DelegatorPCVDeposit is PCVDeposit {
using SafeERC20 for IERC20;

event DelegateUpdate(address indexed oldDelegate, address indexed newDelegate);

/// @notice the token that is being used for voting
ERC20Votes public token;

/// @notice the snapshot delegate for the deposit
address public delegate;

/// @notice Delegator PCV Deposit constructor
/// @param _core Fei Core for reference
/// @param _token token to custody and delegate with
/// @param _initialDelegate the initial delegate
constructor(
address _core,
address _token,
address _initialDelegate
) CoreRef(_core) {
token = ERC20Votes(_token);
if (_initialDelegate != address(0)) _delegate(_initialDelegate);
}

/// @notice withdraw tokens from the PCV allocation
/// @param amount of tokens withdrawn
/// @param to the address to send PCV to
function withdraw(address to, uint256 amount) external virtual override onlyPCVController {
IERC20(token).safeTransfer(to, amount);
emit Withdrawal(msg.sender, to, amount);
}

/// @notice no-op
function deposit() external override {}

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

/// @notice display the related token of the balance reported
function balanceReportedIn() public view override returns (address) {
return address(token);
}

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

function _delegate(address newDelegate) internal {
address oldDelegate = delegate;
delegate = newDelegate;

token.delegate(delegate);

emit DelegateUpdate(oldDelegate, newDelegate);
}
}
140 changes: 140 additions & 0 deletions contracts/metagov/VlAuraDelegatorPCVDeposit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import "../core/TribeRoles.sol";
import "./DelegatorPCVDeposit.sol";

interface IAuraLocker {
struct LockedBalance {
uint112 amount;
uint32 unlockTime;
}
struct EarnedData {
address token;
uint256 amount;
}

function balanceOf(address _user) external view returns (uint256);

function lock(address _account, uint256 _amount) external;

function getReward(address _account, bool _stake) external;

function processExpiredLocks(bool _relock) external;

function emergencyWithdraw() external;

function delegates(address account) external view returns (address);

function getVotes(address account) external view returns (uint256);

function lockedBalances(address _user)
external
view
returns (
uint256 total,
uint256 unlockable,
uint256 locked,
LockedBalance[] memory lockData
);

function claimableRewards(address _account) external view returns (EarnedData[] memory userRewards);

function notifyRewardAmount(address _rewardsToken, uint256 _reward) external;
}

interface IAuraMerkleDrop {
function claim(
bytes32[] calldata _proof,
uint256 _amount,
bool _lock
) external returns (bool);
}

/// @title Vote-locked AURA PCVDeposit
/// This contract is a derivative of the DelegatorPCVDeposit contract, that performs an
/// on-chain delegation. This contract is meant to hold AURA and vlAURA tokens, and allow
/// locking of AURA to vlAURA and renew vlAURA locks, or exit vlAURA locks to get back
/// liquid AURA. This contract can also claim vlAURA rewards.
/// The first version of this contract also allows claiming of the AURA airdrop.
/// @author eswak
contract VlAuraDelegatorPCVDeposit is DelegatorPCVDeposit {
using SafeERC20 for IERC20;

address public aura;
address public auraLocker;
address public auraMerkleDrop;

/// @notice constructor
/// @param _core Fei Core for reference
constructor(address _core)
DelegatorPCVDeposit(
_core,
address(0), // token
address(0) // initialDelegate
)
{}

// At deploy time, Aura Protocol wasn't live yet, so we need to set the
// contract addresses manually, not in the constructor.
function initialize(
address _aura,
address _auraLocker,
address _auraMerkleDrop
) external {
require(
aura == address(0) ||
auraLocker == address(0) ||
auraMerkleDrop == address(0) ||
address(token) == address(0),
"initialized"
);

aura = _aura;
auraLocker = _auraLocker;
auraMerkleDrop = _auraMerkleDrop;
token = ERC20Votes(_auraLocker);
}

/// @notice noop, vlAURA can't be transferred.
/// wait for lock expiry, and call withdrawERC20 on AURA.
function withdraw(address, uint256) external override {}

/// @notice returns the balance of locked + unlocked
function balance() public view virtual override returns (uint256) {
return IERC20(aura).balanceOf(address(this)) + IERC20(auraLocker).balanceOf(address(this));
}

/// @notice claim AURA airdrop and vote-lock it for 16 weeks
/// this function is not access controlled & can be called by anyone.
function claimAirdropAndLock(bytes32[] calldata _proof, uint256 _amount) external returns (bool) {
return IAuraMerkleDrop(auraMerkleDrop).claim(_proof, _amount, true);
}

/// @notice lock AURA held on this contract to vlAURA
function lock() external whenNotPaused onlyTribeRole(TribeRoles.METAGOVERNANCE_TOKEN_STAKING) {
uint256 amount = IERC20(aura).balanceOf(address(this));
IERC20(aura).safeApprove(auraLocker, amount);
IAuraLocker(auraLocker).lock(address(this), amount);
}

/// @notice refresh lock after it has expired
function relock() external whenNotPaused onlyTribeRole(TribeRoles.METAGOVERNANCE_TOKEN_STAKING) {
IAuraLocker(auraLocker).processExpiredLocks(true);
}

/// @notice exit lock after it has expired
function unlock() external whenNotPaused onlyTribeRole(TribeRoles.METAGOVERNANCE_TOKEN_STAKING) {
IAuraLocker(auraLocker).processExpiredLocks(false);
}

/// @notice emergency withdraw if system is shut down
function emergencyWithdraw() external whenNotPaused onlyTribeRole(TribeRoles.METAGOVERNANCE_TOKEN_STAKING) {
IAuraLocker(auraLocker).emergencyWithdraw();
}

/// @notice get rewards & stake them (rewards claiming is permissionless)
function getReward() external {
IAuraLocker(auraLocker).getReward(address(this), true);
}
}
66 changes: 66 additions & 0 deletions contracts/pcv/ERC20HoldingPCVDeposit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {PCVDeposit} from "./PCVDeposit.sol";
import {CoreRef} from "../refs/CoreRef.sol";
import {Constants} from "../Constants.sol";

/// @title ERC20HoldingPCVDeposit
/// @notice PCVDeposit that is used to hold ERC20 tokens as a safe harbour. Deposit is a no-op
contract ERC20HoldingPCVDeposit is PCVDeposit {
using SafeERC20 for IERC20;

/// @notice Token which the balance is reported in
IERC20 immutable token;

/// @notice Fei ERC20 token address
address private constant FEI = 0x956F47F50A910163D8BF957Cf5846D573E7f87CA;

constructor(address _core, IERC20 _token) CoreRef(_core) {
require(address(_token) != FEI, "FEI not supported");
token = _token;
}

/// @notice Empty receive function to receive ETH
receive() external payable {}

/////// READ-ONLY Methods /////////////

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

/// @notice returns the resistant balance and FEI in the deposit
function resistantBalanceAndFei() public view override returns (uint256, uint256) {
return (balance(), 0);
}

/// @notice display the related token of the balance reported
function balanceReportedIn() public view override returns (address) {
return address(token);
}

/// @notice No-op deposit
function deposit() external override whenNotPaused {
emit Deposit(msg.sender, balance());
}

/// @notice Withdraw underlying
/// @param amountUnderlying of tokens withdrawn
/// @param to the address to send PCV to
function withdraw(address to, uint256 amountUnderlying) external override onlyPCVController whenNotPaused {
token.safeTransfer(to, amountUnderlying);
emit Withdrawal(msg.sender, to, amountUnderlying);
}

/// @notice Wraps all ETH held by the contract to WETH. Permissionless, anyone can call it
function wrapETH() public {
uint256 ethBalance = address(this).balance;
if (ethBalance != 0) {
Constants.WETH.deposit{value: ethBalance}();
}
}
}
Loading

0 comments on commit f030292

Please sign in to comment.