Skip to content

Commit

Permalink
feat: added strategy with ape template
Browse files Browse the repository at this point in the history
  • Loading branch information
bufander committed Sep 13, 2022
1 parent ee7625a commit 713f26a
Show file tree
Hide file tree
Showing 21 changed files with 1,786 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .env.example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export ETHERSCAN_TOKEN=
export WEB3_ALCHEMY_PROJECT_ID=
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.sol linguist-language=Solidity
*.vy linguist-language=Python
38 changes: 38 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: lint

on:
workflow_dispatch:
push:
branches:
- master
- develop
pull_request:

jobs:
solidity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install node.js dependencies
run: yarn --frozen-lockfile
- name: Run linter on *.sol and *.json
run: yarn lint:check
commits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v5
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.8
- run: pip install -r requirements-dev.txt
- run: black --check --include "(tests|scripts)" .
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: tests

on:
workflow_dispatch:
push:
branches:
- master
- develop
pull_request:

jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: ApeWorX/github-action@v1
- run: pip install git+https://github.com/vyperlang/[email protected]
- run: ape compile --force --size
- run: npm install hardhat
- run: ape test
timeout-minutes: 10
env:
WEB3_ALCHEMY_PROJECT_ID: ${{ secrets.WEB3_ALCHEMY_PROJECT_ID }}


2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.build
.cache
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022 bufander

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
37 changes: 37 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: ape-strategy-mix

plugins:
- name: solidity
- name: vyper
- name: hardhat
- name: alchemy
- name: etherscan

default_ecosystem: ethereum
dependencies:
- name: openzeppelin
github: OpenZeppelin/openzeppelin-contracts
version: 4.7.3
# TODO: Use https://github.com/jmonteer/yearn-vaults-v3 when tag available
- name: bufander
github: bufander/yearn-vaults-v3
version: 0.0.3

solidity:
import_remapping:
- "@openzeppelin/contracts=openzeppelin/v4.7.3"
# TODO: Use https://github.com/jmonteer/yearn-vaults-v3 when tag available
- "@yearnvaultsv3=bufander/v0.0.3"

ethereum:
default_network: mainnet-fork
# mainnet-fork:
# default_provider: hardhat

hardhat:
fork:
ethereum:
mainnet:
upstream_provider: alchemy

#test:
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {extends: ["@commitlint/config-conventional"]};
137 changes: 137 additions & 0 deletions contracts/Strategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity 0.8.14;
pragma experimental ABIEncoderV2;

import {ERC4626BaseStrategy, IERC20} from "@yearnvaultsv3/test/ERC4626BaseStrategy.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";

import "./interfaces/ILendingPool.sol";
import "./interfaces/ILendingPoolAddressesProvider.sol";
import "./interfaces/IProtocolDataProvider.sol";
import "./interfaces/IVault.sol";

contract Strategy is ERC4626BaseStrategy {
using Math for uint256;

IProtocolDataProvider public constant protocolDataProvider =
IProtocolDataProvider(0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d);

address public aToken;

constructor(
address _vault,
string memory _strategyName,
string memory _strategySymbol
)
ERC4626BaseStrategy(_vault, IVault(_vault).asset())
ERC20(_strategyName, _strategySymbol)
{
(address _aToken, , ) = protocolDataProvider.getReserveTokensAddresses(
IVault(_vault).asset()
);
aToken = _aToken;
}

function maxDeposit(address receiver)
public
view
virtual
override
returns (uint256 maxAssets)
{
maxAssets = type(uint256).max;
}

function _freeFunds(uint256 _amount)
internal
override
returns (uint256 _amountFreed)
{
uint256 idle_amount = balanceOfAsset();
if (_amount <= idle_amount) {
// we have enough idle assets for the vault to take
_amountFreed = _amount;
} else {
// We need to take from Aave enough to reach _amount
// We run with 'unchecked' as we are safe from underflow
unchecked {
_withdrawFromAave(
Math.min(_amount - idle_amount, balanceOfAToken())
);
}
_amountFreed = balanceOfAsset();
}
}

function totalAssets() public view override returns (uint256) {
return _totalAssets();
}

function _totalAssets() internal view returns (uint256) {
return balanceOfAsset() + balanceOfAToken();
}

function _invest() internal override {
uint256 available_to_invest = balanceOfAsset();
require(available_to_invest > 0, "no funds to invest");
_depositToAave(available_to_invest);
}

function harvestTrigger() public view override returns (bool) {}

function investTrigger() public view override returns (bool) {}

function delegatedAssets()
public
view
override
returns (uint256 _delegatedAssets)
{}

function _protectedTokens()
internal
view
override
returns (address[] memory _protected)
{}

function _depositToAave(uint256 amount) internal {
ILendingPool lp = _lendingPool();
_checkAllowance(address(lp), asset(), amount);
lp.deposit(asset(), amount, address(this), 0);
}

function _withdrawFromAave(uint256 amount) internal {
ILendingPool lp = _lendingPool();
_checkAllowance(address(lp), aToken, amount);
lp.withdraw(asset(), amount, address(this));
}

function _checkAllowance(
address _contract,
address _token,
uint256 _amount
) internal {
if (IERC20(_token).allowance(address(this), _contract) < _amount) {
IERC20(_token).approve(_contract, 0);
IERC20(_token).approve(_contract, _amount);
}
}

function _lendingPool() internal view returns (ILendingPool) {
return
ILendingPool(
protocolDataProvider.ADDRESSES_PROVIDER().getLendingPool()
);
}

function balanceOfAToken() internal view returns (uint256) {
return IERC20(aToken).balanceOf(address(this));
}

function balanceOfAsset() internal view returns (uint256) {
return IERC20(asset()).balanceOf(address(this));
}
}
40 changes: 40 additions & 0 deletions contracts/interfaces/ILendingPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.14;
pragma experimental ABIEncoderV2;

interface ILendingPool {
/**
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
* @param asset The address of the underlying asset to deposit
* @param amount The amount to be deposited
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;

/**
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to Address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
* @return The final amount withdrawn
**/
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
}
13 changes: 13 additions & 0 deletions contracts/interfaces/ILendingPoolAddressesProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.14;

/**
* @title LendingPoolAddressesProvider contract
* @dev Main registry of addresses part of or connected to the protocol, including permissioned roles
* - Acting also as factory of proxies and admin of those, so with right to change its implementations
* - Owned by the Aave Governance
* @author Aave
**/
interface ILendingPoolAddressesProvider {
function getLendingPool() external view returns (address);
}
26 changes: 26 additions & 0 deletions contracts/interfaces/IProtocolDataProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.14;
pragma experimental ABIEncoderV2;

import {ILendingPoolAddressesProvider} from "./ILendingPoolAddressesProvider.sol";

interface IProtocolDataProvider {
struct TokenData {
string symbol;
address tokenAddress;
}

function ADDRESSES_PROVIDER()
external
view
returns (ILendingPoolAddressesProvider);

function getReserveTokensAddresses(address asset)
external
view
returns (
address aTokenAddress,
address stableDebtTokenAddress,
address variableDebtTokenAddress
);
}
34 changes: 34 additions & 0 deletions contracts/interfaces/IStrategyERC4626.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.14;

interface IStrategyERC4626 {
// So indexers can keep track of this
// ****** EVENTS ******

error NoAccess();

error ProtectedToken(address token);

error StrategyAlreadyInitialized();

function vault() external view returns (address _vault);

function harvestTrigger() external view returns (bool);

// - manual: called by governance or guard, behaves similarly to freeFunds but can incur in losses.
// - vault: called by vault.update_debt if vault is on emergencyFreeFunds mode.
// function emergencyFreeFunds(uint256 _amountToWithdraw) external;

// - `investTrigger() -> bool`: returns true when the strategy has available funds to invest and space for them.
function investTrigger() external view returns (bool);

// - `invest()`: strategy will invest loose funds into the strategy. only callable by keepers
function invest() external;

// - `freeFunds(uint256 _amount)`: strategy will free/unlocked funds from the underlying protocol and leave them idle. (called by vault on update_debt)
function freeFunds(uint256 _amount) external returns (uint256 _freeFunds);

function delegatedAssets() external view returns (uint256 _delegatedAssets);

function migrate(address) external;
}
Loading

0 comments on commit 713f26a

Please sign in to comment.