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

OSU 354 - Create a Yield Bearing Strategy #45

Closed
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
12 changes: 4 additions & 8 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = master
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
branch = v4.9.5
[submodule "lib/tokenized-strategy"]
path = lib/tokenized-strategy
url = https://github.com/yearn/tokenized-strategy
[submodule "lib/octant-v2-core"]
path = lib/octant-v2-core
url = https://github.com/golemfoundation/octant-v2-core
[submodule "lib/tokenized-strategy-periphery"]
path = lib/tokenized-strategy-periphery
url = https://github.com/yearn/tokenized-strategy-periphery
url = https://github.com/golemfoundation/tokenized-strategy-periphery
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,16 @@
"forge-std/=lib/forge-std/src/",
"@tokenized-strategy/=./lib/tokenized-strategy/src/",
"@periphery/=./lib/tokenized-strategy-periphery/src/"
],
"wake.compiler.solc.remappings": [
"@gnosis.pm/safe-contracts/=lib/octant-v2-core/lib/safe-smart-account",
"@openzeppelin/contracts-upgradeable/=lib/octant-v2-core/lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/contracts/=lib/octant-v2-core/lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/",
"octant-v2-core/=lib/octant-v2-core/",
"openzeppelin-contracts-upgradeable/=lib/octant-v2-core/lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/octant-v2-core/lib/openzeppelin-contracts/",
"tokenized-strategy-periphery/=lib/tokenized-strategy-periphery/",
"zodiac/=lib/octant-v2-core/lib/zodiac/contracts/"
]
}
37 changes: 24 additions & 13 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
solc = "0.8.23"

remappings = [
"@openzeppelin/=lib/openzeppelin-contracts/",
"forge-std/=lib/forge-std/src/",
"@tokenized-strategy/=lib/tokenized-strategy/src/",
"@periphery/=lib/tokenized-strategy-periphery/src/",
]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
src = "src"
out = "out"
libs = ["node_modules", "lib"]
evm_version = "cancun"
optimizer = true
optimizer_runs = 10_000_000
solc = "0.8.25"
verbosity = 3

[profile.ci]
fuzz = { runs = 5000 }
invariant = { runs = 1000 }
verbosity = 4

[profile.local]
fuzz = { runs = 50 }
invariant = { runs = 10 }
# Speed up compilation and tests during development.
optimizer = true

[rpc_endpoints]
mainnet = "${ETHEREUM_NODE_MAINNET}"

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions lib/octant-v2-core
Submodule octant-v2-core added at e7e7c5
1 change: 0 additions & 1 deletion lib/openzeppelin-contracts
Submodule openzeppelin-contracts deleted from bd325d
1 change: 0 additions & 1 deletion lib/tokenized-strategy
Submodule tokenized-strategy deleted from 828062
9 changes: 9 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@gnosis.pm/safe-contracts/=lib/octant-v2-core/lib/safe-smart-account
@openzeppelin/contracts-upgradeable/=lib/octant-v2-core/lib/openzeppelin-contracts-upgradeable/contracts/
@openzeppelin/contracts/=lib/octant-v2-core/lib/openzeppelin-contracts/contracts/
forge-std/=lib/forge-std/src/
octant-v2-core/=lib/octant-v2-core/
openzeppelin-contracts-upgradeable/=lib/octant-v2-core/lib/openzeppelin-contracts-upgradeable/
openzeppelin-contracts/=lib/octant-v2-core/lib/openzeppelin-contracts/
tokenized-strategy-periphery/=lib/tokenized-strategy-periphery/
zodiac/=lib/octant-v2-core/lib/zodiac/contracts/
107 changes: 70 additions & 37 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.18;

import {BaseStrategy, ERC20} from "@tokenized-strategy/BaseStrategy.sol";
import {BaseStrategy, ERC20} from "octant-v2-core/src/dragons/BaseStrategy.sol";
import {Module} from "zodiac/core/Module.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IStrategyInterface} from "./interfaces/IStrategyInterface.sol";

// Import interfaces for many popular DeFi projects, or add your own!
//import "../interfaces/<protocol>/<Interface>.sol";
Expand All @@ -20,13 +22,51 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol

// NOTE: To implement permissioned functions you can use the onlyManagement, onlyEmergencyAuthorized and onlyKeepers modifiers

contract Strategy is BaseStrategy {
contract Strategy is Module, BaseStrategy {
using SafeERC20 for ERC20;

constructor(
address _asset,
string memory _name
) BaseStrategy(_asset, _name) {}
address public yieldSource;
bool public trigger;
bool public managed;
bool public kept;
bool public emergentizated;

/// @dev Initialize function, will be triggered when a new proxy is deployed
/// @dev owner of this module will the safe multisig that calls setUp function
/// @param initializeParams Parameters of initialization encoded
function setUp(bytes memory initializeParams) public override initializer {
(address _owner, bytes memory data) = abi.decode(initializeParams, (address, bytes));

(
address _tokenizedStrategyImplementation,
address _asset,
address _yieldSource,
address _management,
address _keeper,
address _dragonRouter,
uint256 _maxReportDelay,
string memory _name
) = abi.decode(data, (address, address, address, address, address, address, uint256, string));

__Ownable_init(msg.sender);
__BaseStrategy_init(
_tokenizedStrategyImplementation,
_asset,
_owner,
_management,
_keeper,
_dragonRouter,
_maxReportDelay,
_name
);

yieldSource = _yieldSource;
if (_asset != ETH) ERC20(_asset).approve(_yieldSource, type(uint256).max);

setAvatar(_owner);
setTarget(_owner);
transferOwnership(_owner);
}

/*//////////////////////////////////////////////////////////////
NEEDED TO BE OVERRIDDEN BY STRATEGIST
Expand Down Expand Up @@ -98,11 +138,7 @@ contract Strategy is BaseStrategy {
* @return _totalAssets A trusted and accurate account for the total
* amount of 'asset' the strategy currently holds including idle funds.
*/
function _harvestAndReport()
internal
override
returns (uint256 _totalAssets)
{
function _harvestAndReport() internal override returns (uint256 _totalAssets) {
// TODO: Implement harvesting logic and accurate accounting EX:
//
// if(!TokenizedStrategy.isShutdown()) {
Expand Down Expand Up @@ -135,9 +171,7 @@ contract Strategy is BaseStrategy {
* @param . The address that is withdrawing from the strategy.
* @return . The available amount that can be withdrawn in terms of `asset`
*/
function availableWithdrawLimit(
address /*_owner*/
) public view override returns (uint256) {
function availableWithdrawLimit(address /*_owner*/ ) public view override returns (uint256) {
// NOTE: Withdraw limitations such as liquidity constraints should be accounted for HERE
// rather than _freeFunds in order to not count them as losses on withdraws.

Expand Down Expand Up @@ -171,16 +205,16 @@ contract Strategy is BaseStrategy {
* @param . The address that is depositing into the strategy.
* @return . The available amount the `_owner` can deposit in terms of `asset`
*
function availableDepositLimit(
address _owner
) public view override returns (uint256) {
TODO: If desired Implement deposit limit logic and any needed state variables .
EX:
uint256 totalAssets = TokenizedStrategy.totalAssets();
return totalAssets >= depositLimit ? 0 : depositLimit - totalAssets;
}
*/
* function availableDepositLimit(
* address _owner
* ) public view override returns (uint256) {
* TODO: If desired Implement deposit limit logic and any needed state variables .
*
* EX:
* uint256 totalAssets = TokenizedStrategy.totalAssets();
* return totalAssets >= depositLimit ? 0 : depositLimit - totalAssets;
* }
*/

/**
* @dev Optional function for strategist to override that can
Expand All @@ -203,17 +237,17 @@ contract Strategy is BaseStrategy {
*
* @param _totalIdle The current amount of idle funds that are available to deploy.
*
function _tend(uint256 _totalIdle) internal override {}
*/
* function _tend(uint256 _totalIdle) internal override {}
*/

/**
* @dev Optional trigger to override if tend() will be used by the strategy.
* This must be implemented if the strategy hopes to invoke _tend().
*
* @return . Should return true if tend() should be called by keeper or false if not.
*
function _tendTrigger() internal view override returns (bool) {}
*/
* function _tendTrigger() internal view override returns (bool) {}
*/

/**
* @dev Optional function for a strategist to override that will
Expand All @@ -236,13 +270,12 @@ contract Strategy is BaseStrategy {
*
* @param _amount The amount of asset to attempt to free.
*
function _emergencyWithdraw(uint256 _amount) internal override {
TODO: If desired implement simple logic to free deployed funds.

EX:
_amount = min(_amount, aToken.balanceOf(address(this)));
_freeFunds(_amount);
}

*/
* function _emergencyWithdraw(uint256 _amount) internal override {
* TODO: If desired implement simple logic to free deployed funds.
*
* EX:
* _amount = min(_amount, aToken.balanceOf(address(this)));
* _freeFunds(_amount);
* }
*/
}
87 changes: 36 additions & 51 deletions src/StrategyFactory.sol
Original file line number Diff line number Diff line change
@@ -1,76 +1,61 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "octant-v2-core/src/dragons/DragonTokenizedStrategy.sol";
import "forge-std/console2.sol";
import {Strategy, ERC20} from "./Strategy.sol";
import {IStrategyInterface} from "./interfaces/IStrategyInterface.sol";

contract StrategyFactory {
event NewStrategy(address indexed strategy, address indexed asset);
error InvalidStrategyId();

address public immutable emergencyAdmin;
event NewStrategy(address indexed strategy, uint256 indexed strategyId);

address public management;
address public performanceFeeRecipient;
address public keeper;
/// @notice Track the deployments. strategyId => strategy address
mapping(uint256 => address) public deployments;

/// @notice Track the deployments. asset => pool => strategy
mapping(address => address) public deployments;
address public strategyImplementation;
address public dragonTokenizedStrategyImplementation;
uint256 public strategyId;

constructor(
address _management,
address _performanceFeeRecipient,
address _keeper,
address _emergencyAdmin
) {
management = _management;
performanceFeeRecipient = _performanceFeeRecipient;
keeper = _keeper;
emergencyAdmin = _emergencyAdmin;
constructor() {
strategyId = 0;
strategyImplementation = address(new Strategy());
dragonTokenizedStrategyImplementation = address(new DragonTokenizedStrategy());
}

/**
* @notice Deploy a new Strategy.
* @param _asset The underlying asset for the strategy to use.
* @return . The address of the new strategy.
* @notice Deploy a new Strategy
* @param initializeParams The encoded parameters to initialize the strategy with
* @return address The address of the newly deployed strategy
*/
function newStrategy(
address _asset,
string calldata _name
) external virtual returns (address) {
// tokenized strategies available setters.
IStrategyInterface _newStrategy = IStrategyInterface(
address(new Strategy(_asset, _name))
function newStrategy(bytes memory initializeParams) external virtual returns (address) {
ERC1967Proxy _newStrategy = new ERC1967Proxy(
strategyImplementation,
abi.encodeWithSelector(Strategy(payable(address(0))).setUp.selector, initializeParams)
);

_newStrategy.setPerformanceFeeRecipient(performanceFeeRecipient);
uint256 currentId = strategyId;
deployments[currentId] = address(_newStrategy);

_newStrategy.setKeeper(keeper);
emit NewStrategy(address(_newStrategy), currentId);

_newStrategy.setPendingManagement(management);
unchecked {
strategyId = currentId + 1;
}

_newStrategy.setEmergencyAdmin(emergencyAdmin);

emit NewStrategy(address(_newStrategy), _asset);

deployments[_asset] = address(_newStrategy);
return address(_newStrategy);
}

function setAddresses(
address _management,
address _performanceFeeRecipient,
address _keeper
) external {
require(msg.sender == management, "!management");
management = _management;
performanceFeeRecipient = _performanceFeeRecipient;
keeper = _keeper;
}

function isDeployedStrategy(
address _strategy
) external view returns (bool) {
address _asset = IStrategyInterface(_strategy).asset();
return deployments[_asset] == _strategy;
/**
* @notice Retrieve a deployed strategy address by ID
* @param _strategyId The ID of the strategy to look up
* @return address The strategy contract address
*/
function getStrategy(uint256 _strategyId) external view returns (address) {
address strategy = deployments[_strategyId];
if (strategy == address(0)) revert InvalidStrategyId();
return strategy;
}
}
4 changes: 2 additions & 2 deletions src/interfaces/IStrategyInterface.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.18;

import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol";
import {IStrategy} from "octant-v2-core/src/interfaces/IStrategy.sol";

interface IStrategyInterface is IStrategy {
//TODO: Add your specific implementation interface in here.
function setUp(bytes memory initializeParams) external;
}
Loading