Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion contract-addresses/mainnetAddresses.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,10 @@
"rariPool8FeiIrmAddress": {"address": "0x8f47be5692180079931e2f983db6996647aba0a5" },
"rariPool8TribeIrmAddress": {"address": "0x075538650a9c69ac8019507a7dd1bd879b12c1d7" },
"rariPool8EthIrmAddress": {"address": "0xbab47e4b692195bf064923178a90ef999a15f819" },
"rariPool8DaiIrmAddress": {"address": "0xede47399e2aa8f076d40dc52896331cba8bd40f7" }
"rariPool8DaiIrmAddress": {"address": "0xede47399e2aa8f076d40dc52896331cba8bd40f7" },
"aWETHAddress" : {"address": "0x030bA81f1c18d280636F32af80b9AAd02Cf0854e"},
"stAAVEAddress" : {"artifact" : "IERC20", "address": "0x4da27a545c0c5b758a6ba100e3a049001de870f5" },
"aaveLendingPool" : {"address": "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9"},
"aaveTribeIncentivesController" : {"address": "0xFF865335401F12B88fa3FF5A3a51685A7f224191"},
"aaveIncentivesController" : {"address": "0xd784927ff2f95ba542bfc824c8a8a98f3495f6b5"}
}
4 changes: 4 additions & 0 deletions contracts/mock/MockERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ contract MockERC20 is ERC20, ERC20Burnable {
_mint(account, amount);
return true;
}

function approveOverride(address owner, address spender, uint256 amount) public {
_approve(owner, spender, amount);
}
}
28 changes: 28 additions & 0 deletions contracts/mock/MockLendingPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import "./MockERC20.sol";

/// @title Aave PCV Deposit
/// @author Fei Protocol
contract MockLendingPool {

MockERC20 public aToken;

constructor() {
aToken = new MockERC20();
}

function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external {
IERC20(asset).transferFrom(msg.sender, address(this), amount);
aToken.mint(onBehalfOf, amount);
}

function withdraw(address asset, uint256 amount, address to) external {
aToken.approveOverride(msg.sender, address(this), amount);
aToken.burnFrom(msg.sender, amount);

IERC20(asset).transfer(to, amount);
}

}
98 changes: 98 additions & 0 deletions contracts/pcv/aave/AavePCVDeposit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import "../utils/WethPCVDeposit.sol";

interface LendingPool {
function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;

function withdraw(address asset, uint256 amount, address to) external;
}

interface IncentivesController {
function claimRewards(address[] calldata assets, uint256 amount, address to) external;

function getRewardsBalance(address[] calldata assets, address user) external view returns(uint256);
}

/// @title Aave PCV Deposit
/// @author Fei Protocol
contract AavePCVDeposit is WethPCVDeposit {

event ClaimRewards(address indexed caller, uint256 amount);

/// @notice the associated Aave aToken for the deposit
IERC20 public aToken;

/// @notice the Aave v2 lending pool
LendingPool public lendingPool;

/// @notice the underlying token of the PCV deposit
IERC20 public token;

/// @notice the Aave incentives controller for the aToken
IncentivesController public incentivesController;

/// @notice Aave PCV Deposit constructor
/// @param _core Fei Core for reference
/// @param _lendingPool the Aave v2 lending pool
/// @param _token the underlying token of the PCV deposit
/// @param _aToken the associated Aave aToken for the deposit
/// @param _incentivesController the Aave incentives controller for the aToken
constructor(
address _core,
LendingPool _lendingPool,
IERC20 _token,
IERC20 _aToken,
IncentivesController _incentivesController
) CoreRef(_core) {
lendingPool = _lendingPool;
aToken = _aToken;
token = _token;
incentivesController = _incentivesController;
}

/// @notice claims Aave rewards from the deposit and transfers to this address
function claimRewards() external {
address[] memory assets = new address[](1);
assets[0] = address(aToken);
// First grab the available balance
uint256 amount = incentivesController.getRewardsBalance(assets, address(this));

// claim all available rewards
incentivesController.claimRewards(assets, amount, address(this));

emit ClaimRewards(msg.sender, amount);
}

/// @notice deposit buffered aTokens
function deposit() external override whenNotPaused {
// wrap any held ETH if present
wrapETH();

// Approve and deposit buffered tokens
uint256 pendingBalance = token.balanceOf(address(this));
token.approve(address(lendingPool), pendingBalance);
lendingPool.deposit(address(token), pendingBalance, address(this), 0);

emit Deposit(msg.sender, pendingBalance);
}

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

/// @notice returns total balance of PCV in the Deposit
/// @dev aTokens are rebasing, so represent 1:1 on underlying value
function balance() public view override returns (uint256) {
return aToken.balanceOf(address(this));
}
}
33 changes: 33 additions & 0 deletions deploy/aavePCVDeposit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const AavePCVDeposit = artifacts.require('AavePCVDeposit');

async function deploy(deployAddress, addresses, logging = false) {
const {
coreAddress,
wethAddress,
aaveLendingPool,
aWETHAddress,
aaveIncentivesController
} = addresses;

if (
!coreAddress || !wethAddress || !aaveLendingPool || !aWETHAddress || !aaveIncentivesController
) {
throw new Error('An environment variable contract address is not set');
}

const aaveEthPCVDeposit = await AavePCVDeposit.new(
coreAddress,
aaveLendingPool,
wethAddress,
aWETHAddress,
aaveIncentivesController,
{ from: deployAddress }
);
logging && console.log('Aave ETH PCV Deposit deployed to: ', aaveEthPCVDeposit.address);

return {
aaveEthPCVDeposit
};
}

module.exports = { deploy };
32 changes: 32 additions & 0 deletions end-to-end/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,38 @@ describe('e2e', function () {
expect((await ethCompoundPCVDeposit.balance()).sub(balanceBefore)).to.be.bignumber.lessThan(amount);
})
})

describe('Aave', async () => {
it('should be able to deposit and withdraw ETH', async function () {
const aaveEthPCVDeposit = contracts.aaveEthPCVDeposit;
const amount = tenPow18.mul(toBN(200));
await web3.eth.sendTransaction({from: deployAddress, to: aaveEthPCVDeposit.address, value: amount });

const balanceBefore = await aaveEthPCVDeposit.balance();

await aaveEthPCVDeposit.deposit();
expectApprox((await aaveEthPCVDeposit.balance()).sub(balanceBefore), amount, '100');

await aaveEthPCVDeposit.withdraw(deployAddress, amount);

expect((await aaveEthPCVDeposit.balance()).sub(balanceBefore)).to.be.bignumber.lessThan(amount);
})

it('should be able to earn and claim stAAVE', async () => {
const aaveEthPCVDeposit = contracts.aaveEthPCVDeposit;
const amount = tenPow18.mul(toBN(200));
await web3.eth.sendTransaction({from: deployAddress, to: aaveEthPCVDeposit.address, value: amount });

const aaveBalanceBefore = await contracts.stAAVE.balanceOf(aaveEthPCVDeposit.address);
await aaveEthPCVDeposit.deposit();

await aaveEthPCVDeposit.claimRewards();
const aaveBalanceAfter = await contracts.stAAVE.balanceOf(aaveEthPCVDeposit.address);

expect(aaveBalanceAfter.sub(aaveBalanceBefore)).to.be.bignumber.greaterThan(toBN(0));
});
})

describe('Access control', async () => {
before(async () => {
// Revoke deploy address permissions, so that does not erroneously
Expand Down
2 changes: 2 additions & 0 deletions end-to-end/setup/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type MainnetContracts = {
rariPool8FeiPCVDeposit: typeof Contract,
rariPool8EthPCVDeposit: typeof Contract,
compoundEthPCVDeposit: typeof Contract,
aaveEthPCVDeposit: typeof Contract,
stAAVE: typeof Contract,
dpiBondingCurve: typeof Contract,
dpi: typeof Contract,
chainlinkDpiUsdOracleWrapper: typeof Contract,
Expand Down
2 changes: 1 addition & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const config: HardhatUserConfig = {
chainId: 5777, // Any network (default: none)
forking: {
url: `https://eth-mainnet.alchemyapi.io/v2/${mainnetAlchemyApiKey}`,
blockNumber: 13018540
blockNumber: 13019420
}
},
localhost: {
Expand Down
5 changes: 5 additions & 0 deletions proposals/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@
"proposerAddress" : "0xe0ac4559739bD36f0913FB0A3f5bFC19BCBaCD52",
"voterAddress" : "0xB8f482539F2d3Ae2C9ea6076894df36D1f632775",
"proposal_calldata" : ""
},
"aavePCVDeposit" : {
"deploy" : true,
"exec" : false,
"validate" : false
}
}
18 changes: 18 additions & 0 deletions proposals/dao/aavePCVDeposit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const e18 = '000000000000000000';

async function setup(addresses, oldContracts, contracts, logging) {}

// The DAO steps for creating an Aave ETH PCV deposit
async function run(addresses, oldContracts, contracts, logging = false) {
const {
aaveEthPCVDeposit,
ethPCVDripper
} = contracts;

await ethPCVDripper.withdrawETH(aaveEthPCVDeposit.address, `1000${e18}`);
await aaveEthPCVDeposit.deposit();
}

async function teardown(addresses, oldContractAddresses, logging) {}

module.exports = { setup, run, teardown };
119 changes: 119 additions & 0 deletions test/pcv/AavePCVDeposit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const {
web3,
BN,
expectRevert,
balance,
expect,
getAddresses,
getCore,
} = require('../helpers');

const AavePCVDeposit = artifacts.require('AavePCVDeposit');
const MockLendingPool = artifacts.require('MockLendingPool');
const MockERC20 = artifacts.require('MockERC20');

describe('AavePCVDeposit', function () {
let userAddress;
let pcvControllerAddress;
let governorAddress;

beforeEach(async function () {
({
userAddress,
pcvControllerAddress,
governorAddress
} = await getAddresses());

this.core = await getCore(true);

this.lendingPool = await MockLendingPool.new();
this.token = await MockERC20.new();
this.aToken = await MockERC20.at(await this.lendingPool.aToken());

this.aavePCVDeposit = await AavePCVDeposit.new(
this.core.address,
this.lendingPool.address,
this.token.address,
this.aToken.address,
this.token.address // filling in dummy address for incentives controller
);

this.depositAmount = new BN('1000000000000000000');
});

describe('Deposit', function() {
describe('Paused', function() {
it('reverts', async function() {
await this.aavePCVDeposit.pause({from: governorAddress});
await expectRevert(this.aavePCVDeposit.deposit(), 'Pausable: paused');
});
});

describe('Not Paused', function() {
beforeEach(async function() {
await this.token.mint(this.aavePCVDeposit.address, this.depositAmount);
});

it('succeeds', async function() {
expect(await this.aavePCVDeposit.balance()).to.be.bignumber.equal(new BN('0'));
await this.aavePCVDeposit.deposit();
// Balance should increment with the new deposited aTokens underlying
expect(await this.aavePCVDeposit.balance()).to.be.bignumber.equal(this.depositAmount);

// Held balance should be 0, now invested into Aave
expect(await this.token.balanceOf(this.aavePCVDeposit.address)).to.be.bignumber.equal(new BN('0'));
});
});
});

describe('Withdraw', function() {
beforeEach(async function() {
await this.token.mint(this.aavePCVDeposit.address, this.depositAmount);
await this.aavePCVDeposit.deposit();
});

describe('Not PCVController', function() {
it('reverts', async function() {
await expectRevert(this.aavePCVDeposit.withdraw(userAddress, this.depositAmount, {from: userAddress}), 'CoreRef: Caller is not a PCV controller');
});
});

it('succeeds', async function() {
const userBalanceBefore = await this.token.balanceOf(userAddress);

// withdrawing should take balance back to 0
expect(await this.aavePCVDeposit.balance()).to.be.bignumber.equal(this.depositAmount);
await this.aavePCVDeposit.withdraw(userAddress, this.depositAmount, {from: pcvControllerAddress});
expect(await this.aavePCVDeposit.balance()).to.be.bignumber.equal(new BN('0'));

const userBalanceAfter = await this.token.balanceOf(userAddress);

expect(userBalanceAfter.sub(userBalanceBefore)).to.be.bignumber.equal(this.depositAmount);
});
});

describe('WithdrawERC20', function() {
describe('Not PCVController', function() {
it('reverts', async function() {
await expectRevert(this.aavePCVDeposit.withdrawERC20(this.aToken.address, userAddress, this.depositAmount, {from: userAddress}), 'CoreRef: Caller is not a PCV controller');
});
});

describe('From PCVController', function() {
beforeEach(async function() {
await this.token.mint(this.aavePCVDeposit.address, this.depositAmount);
await this.aavePCVDeposit.deposit();
});

it('succeeds', async function() {
expect(await this.aavePCVDeposit.balance()).to.be.bignumber.equal(this.depositAmount);
await this.aavePCVDeposit.withdrawERC20(this.aToken.address, userAddress, this.depositAmount.div(new BN('2')), {from: pcvControllerAddress});

// balance should also get cut in half
expect(await this.aavePCVDeposit.balance()).to.be.bignumber.equal(this.depositAmount.div(new BN('2')));

expect(await this.aToken.balanceOf(userAddress)).to.be.bignumber.equal(this.depositAmount.div(new BN('2')));
});
});
});
});