Skip to content

Commit

Permalink
build: updates to base (#67)
Browse files Browse the repository at this point in the history
* build: delegate internal function

* feat: virtualize

* feat: move asset getter

* feat: init revert messages

* fix: tend trigger

* chore: interface

* chore: rename base strategy

* fix: comments

* fix: typo

* fix: lint

* fix: naming

* fix: format
  • Loading branch information
Schlagonia authored Sep 28, 2023
1 parent a288bd5 commit b6f3115
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 142 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ This repository contains the base code for the Yearn V3 "Tokenized Strategy" imp

The implementation address that calls are delegated to is pre-set to a constant and can never be changed post deployment. The implementation contract itself is ownerless and can never be updated in any way.

NOTE: The master branch has these pre-set addresses set based on the deterministic address that testing on a local device will render. These contracts should not be used in production and any live versions should use an official [release](https://github.com/yearn/tokenized-strategy/releases).
NOTE: The master branch has these pre-set addresses set based on the deterministic address that testing on a local device will render. These contracts should NOT be used in production and any live versions should use an official [release](https://github.com/yearn/tokenized-strategy/releases).

A Strategy contract can become a fully ERC-4626 compliant vault by inheriting the `BaseTokenizedStrategy` contract, that uses the fallback function to delegateCall the previously deployed version of `TokenizedStrategy`. A strategist then only needs to override three simple functions in their specific strategy.
A Strategy contract can become a fully ERC-4626 compliant vault by inheriting the `BaseStrategy` contract, that uses the fallback function to delegateCall the previously deployed version of `TokenizedStrategy`. A strategist then only needs to override three simple functions in their specific strategy.

[TokenizedStrategy](https://github.com/yearn/tokenized-strategy/blob/master/src/TokenizedStrategy.sol) - The implementation contract that holds all logic for every strategy.

[BaseTokenizedStrategy](https://github.com/yearn/tokenized-strategy/blob/master/src/BaseTokenizedStrategy.sol) - Abstract contract to inherit that communicates with the `TokenizedStrategy`.
[BaseStrategy](https://github.com/yearn/tokenized-strategy/blob/master/src/BaseStrategy.sol) - Abstract contract to inherit that communicates with the `TokenizedStrategy`.

Full tech spech can be found [here](https://github.com/yearn/tokenized-strategy/blob/master/SPECIFICATION.md)

Expand Down
102 changes: 64 additions & 38 deletions src/BaseTokenizedStrategy.sol → src/BaseStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.18;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// TokenizedStrategy interface used for internal view delegateCalls.
import {ITokenizedStrategy} from "./interfaces/ITokenizedStrategy.sol";

/**
* @title Yearn Base Tokenized Strategy
* @title YearnV3 Base Strategy
* @author yearn.finance
* @notice
* BaseTokenizedStrategy implements all of the required functionality to
* BaseStrategy implements all of the required functionality to
* seamlessly integrate with the `TokenizedStrategy` implementation contract
* allowing anyone to easily build a fully permisionless ERC-4626 compliant
* allowing anyone to easily build a fully permissionless ERC-4626 compliant
* Vault by inheriting this contract and overriding three simple functions.
* It utilizes an immutable proxy pattern that allows the BaseTokenizedStrategy
* It utilizes an immutable proxy pattern that allows the BaseStrategy
* to remain simple and small. All standard logic is held within the
* `TokenizedStrategy` and is reused over any n strategies all using the
* `fallback` function to delegatecall the implementation so that strategists
* can only be concerned with writing their strategy specific code.
*
* This contract should be inherited and the three main abstract methods
* `_deployFunds`, `_freeFunds` and `_harvestAndReport` implemented to adapt
* the Strategy to the particular needs it has to create a return. There are
* other optional methods that can be implemented to further customize of
* the Strategy to the particular needs it has to generate yield. There are
* other optional methods that can be implemented to further customize
* the strategy if desired.
*
* All default storage for the strategy is controlled and updated by the
Expand All @@ -33,7 +35,7 @@ import {ITokenizedStrategy} from "./interfaces/ITokenizedStrategy.sol";
* can be viewed within the Strategy by a simple call using the
* `TokenizedStrategy` variable. IE: TokenizedStrategy.globalVariable();.
*/
abstract contract BaseTokenizedStrategy {
abstract contract BaseStrategy {
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -115,7 +117,8 @@ abstract contract BaseTokenizedStrategy {
ITokenizedStrategy internal immutable TokenizedStrategy;

// Underlying asset the Strategy is earning yield on.
address public immutable asset;
// Stored here for cheap retrievals wihtin the strategy.
ERC20 internal immutable asset;

/**
* @notice Used to initialize the strategy on deployment.
Expand All @@ -129,13 +132,18 @@ abstract contract BaseTokenizedStrategy {
* @param _name Name the strategy will use.
*/
constructor(address _asset, string memory _name) {
asset = _asset;
asset = ERC20(_asset);

// Set instance of the implementation for internal use.
TokenizedStrategy = ITokenizedStrategy(address(this));

// Initilize the strategies storage variables.
_init(_asset, _name, msg.sender, msg.sender, msg.sender);
_delegateCall(
abi.encodeCall(
ITokenizedStrategy.init,
(_asset, _name, msg.sender, msg.sender, msg.sender)
)
);

// Store the tokenizedStrategyAddress at the standard implementation
// address storage slot so etherscan picks up the interface. This gets
Expand Down Expand Up @@ -246,16 +254,30 @@ abstract contract BaseTokenizedStrategy {
function _tend(uint256 _totalIdle) internal virtual {}

Check warning on line 254 in src/BaseStrategy.sol

View workflow job for this annotation

GitHub Actions / solidity

Code contains empty blocks

/**
* @notice Returns weather or not tend() should be called by a keeper.
* @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() external view virtual returns (bool) {
function _tendTrigger() internal view virtual returns (bool) {
return false;
}

/**
* @notice Returns if tend() should be called by a keeper.
*
* @return . Should return true if tend() should be called by keeper or false if not.
* @return . Calldata for the tend call.
*/
function tendTrigger() external view virtual returns (bool, bytes memory) {
return (
// Return the status of the tend trigger.
_tendTrigger(),
// And the needed calldata either way.
abi.encodeWithSelector(ITokenizedStrategy.tend.selector)
);
}

/**
* @notice Gets the max amount of `asset` that an address can deposit.
* @dev Defaults to an unlimited amount for any address. But can
Expand Down Expand Up @@ -348,7 +370,7 @@ abstract contract BaseTokenizedStrategy {
* @param _amount The amount of 'asset' that the strategy should
* attemppt to deposit in the yield source.
*/
function deployFunds(uint256 _amount) external onlySelf {
function deployFunds(uint256 _amount) external virtual onlySelf {
_deployFunds(_amount);
}

Expand All @@ -362,7 +384,7 @@ abstract contract BaseTokenizedStrategy {
*
* @param _amount The amount of 'asset' that the strategy should attempt to free up.
*/
function freeFunds(uint256 _amount) external onlySelf {
function freeFunds(uint256 _amount) external virtual onlySelf {
_freeFunds(_amount);
}

Expand All @@ -378,7 +400,7 @@ abstract contract BaseTokenizedStrategy {
* @return . A trusted and accurate account for the total amount
* of 'asset' the strategy currently holds including idle funds.
*/
function harvestAndReport() external onlySelf returns (uint256) {
function harvestAndReport() external virtual onlySelf returns (uint256) {
return _harvestAndReport();
}

Expand All @@ -395,7 +417,7 @@ abstract contract BaseTokenizedStrategy {
* @param _totalIdle The amount of current idle funds that can be
* deployed during the tend
*/
function tendThis(uint256 _totalIdle) external onlySelf {
function tendThis(uint256 _totalIdle) external virtual onlySelf {
_tend(_totalIdle);
}

Expand All @@ -412,36 +434,40 @@ abstract contract BaseTokenizedStrategy {
*
* @param _amount The amount of asset to attempt to free.
*/
function shutdownWithdraw(uint256 _amount) external onlySelf {
function shutdownWithdraw(uint256 _amount) external virtual onlySelf {
_emergencyWithdraw(_amount);
}

/**
* @dev Funciton used on initialization to delegate call the
* TokenizedStrategy to setup the default storage for the strategy.
* @dev Funciton used to delegate call the TokenizedStrategy with
* certain `_calldata` and return any return values.
*
* We cannot use the `TokenizedStrategy` variable call since this
* contract is not deployed fully yet. So we need to manually
* delegateCall the TokenizedStrategy.
* This is used to setup the intial storage of the strategy, and
* can be used by strategist to forward any other call to the
* TokenizedStrategy implementation.
*
* This is the only time an internal delegateCall should not
* be for a view function
* @param _calldata The abi encoded calldata to use in delegatecall.
* @return . The return value if the call was successful in bytes.
*/
function _init(
address _asset,
string memory _name,
address _management,
address _performanceFeeRecipient,
address _keeper
) private {
(bool success, ) = tokenizedStrategyAddress.delegatecall(
abi.encodeCall(
ITokenizedStrategy.init,
(_asset, _name, _management, _performanceFeeRecipient, _keeper)
)
);
function _delegateCall(
bytes memory _calldata
) internal returns (bytes memory) {
// Delegate call the tokenized strategy with provided calldata.
(bool success, bytes memory result) = tokenizedStrategyAddress

Check warning on line 456 in src/BaseStrategy.sol

View workflow job for this annotation

GitHub Actions / solidity

Avoid to use low level calls
.delegatecall(_calldata);

// If the call reverted. Return the error.
if (!success) {
assembly {

Check warning on line 461 in src/BaseStrategy.sol

View workflow job for this annotation

GitHub Actions / solidity

Avoid to use inline assembly. It is acceptable only in rare cases
let ptr := mload(0x40)
let size := returndatasize()
returndatacopy(ptr, 0, size)
revert(ptr, size)
}
}

require(success, "init failed");
// Return the result.
return result;
}

// exeute a function on the TokenizedStrategy and return any value.
Expand Down
Loading

0 comments on commit b6f3115

Please sign in to comment.