Skip to content

Commit

Permalink
build: multi chain updates (#50)
Browse files Browse the repository at this point in the history
* build: report trigger fixes

* chore: readme updates

* build: oracle updates (#51)

* build: apr changes

* fix: dont revert

* chore: check for v3 vault

* build: oracle updates (#52)

* build: apr changes

* fix: dont revert

* chore: comments

* fix: add delta to weighted

* build: add delta

* fix: github action

* chore: use weighted avg
  • Loading branch information
Schlagonia authored Oct 17, 2024
1 parent b5038c9 commit f139be6
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ jobs:
with:
submodules: recursive

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install Vyper
run: pip install vyper==0.3.7

Expand Down
19 changes: 4 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ NOTE: If you are on a windows machine it is recommended to use [WSL](https://lea

cd tokenized-strategy-periphery

pip install vyper==0.3.7

yarn


Expand All @@ -24,7 +26,7 @@ NOTE: you can use other services.

Use .env file
1. Make a copy of `.env.example`
2. Add the values for `ETH_RPC_URL`, `ETHERSCAN_API_KEY` and other example vars
2. Add the values for `ETH_RPC_URL`
NOTE: If you set up a global environment variable, that will take precedence.


Expand All @@ -43,21 +45,8 @@ make test

Deployment of periphery contracts such as the [Apr Oracle](https://github.com/yearn/tokenized-strategy-periphery/blob/master/src/AprOracle/AprOracle.sol) or [Common Report Trigger](https://github.com/yearn/tokenized-strategy-periphery/blob/master/src/ReportTrigger/CommonReportTrigger.sol) are done using a create2 factory in order to get a deterministic address that is the same on each EVM chain.

This can be done permissionlessly if the most recent contract has not yet been deployed on a chain you would like to use it on.

1. If you have not added a keystore private key to foundry before add your address to use

```shell
$ cast wallet import --interactive <wallet_name>
```
This can be done permissionlessly if the most recent contract has not yet been deployed on a chain you would like to use it on using this repo https://github.com/wavey0x/yearn-v3-deployer

2. Run the deployment script for the contract you want to deploy.
```sh
forge script script/DeployContractName.s.sol:DeployContractName --broadcast --rpc-url YOUR_RPC_URL --account ACCOUNT_NAME
```
- You can do a dry run before officially deploying by removing the `--broadcast` flag.
- For chains that don't support 1559 tx's you may need to add a `--legacy` flag.
3. The address the contract was deployed at will print in the console and should match any other chain the same version has been deployed on.

## Swapper helper contracts

Expand Down
2 changes: 0 additions & 2 deletions script/BaseScript.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,5 @@ abstract contract BaseScript is Script {

Deployer public deployer = Deployer(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);

address public v3Safe = 0x33333333D5eFb92f19a5F94a43456b3cec2797AE;

address public initGov = 0x6f3cBE2ab3483EC4BA7B672fbdCa0E9B33F88db8;
}
5 changes: 2 additions & 3 deletions script/DeployAprOracle.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ contract DeployAprOracle is BaseScript {
vm.startBroadcast();

// Encode constructor arguments
bytes memory construct = abi.encode(v3Safe);
bytes memory construct = abi.encode(initGov);

// Get the bytecode
bytes memory bytecode = abi.encodePacked(vm.getCode("AprOracle.sol:AprOracle"), construct);

// Pick an unique salt
bytes32 salt = keccak256("APR Oracle");
bytes32 salt;

address contractAddress = deployer.deployCreate2(salt, bytecode);

Expand Down
24 changes: 0 additions & 24 deletions script/DeployAuctionFactory.sol

This file was deleted.

30 changes: 11 additions & 19 deletions script/DeployCommonTrigger.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,21 @@ import "forge-std/Script.sol";
// Deploy a contract to a deterministic address with create2
contract DeployCommonTrigger is Script {

Deployer public deployer = Deployer(0x8D85e7c9A4e369E53Acc8d5426aE1568198b0112);
Deployer public deployer = Deployer(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);

function run() external {
vm.startBroadcast();

// Encode constructor arguments
bytes memory construct = abi.encode(0x33333333D5eFb92f19a5F94a43456b3cec2797AE);
bytes memory construct = abi.encode(0x6f3cBE2ab3483EC4BA7B672fbdCa0E9B33F88db8);

// Append constructor args to the bytecode
bytes memory bytecode = abi.encodePacked(vm.getCode("CommonReportTrigger.sol:CommonReportTrigger"), construct);

// Use 0 as salt.
bytes32 salt;

// Pick an unique salt
uint256 salt = uint256(keccak256("Common Trigger"));

address contractAddress = deployer.deploy(bytecode, salt);
address contractAddress = deployer.deployCreate2(salt, bytecode);

console.log("Address is ", contractAddress);

Expand All @@ -30,17 +29,10 @@ contract DeployCommonTrigger is Script {
}

contract Deployer {
event Deployed(address addr, uint256 salt);

function deploy(bytes memory code, uint256 salt) external returns (address) {
address addr;
assembly {
addr := create2(0, add(code, 0x20), mload(code), salt)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
emit Deployed(addr, salt);
return addr;
}
event ContractCreation(address indexed newContract, bytes32 indexed salt);

function deployCreate2(
bytes32 salt,
bytes memory initCode
) public payable returns (address newContract) {}
}
98 changes: 82 additions & 16 deletions src/AprOracle/AprOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ contract AprOracle is Governance {
mapping(address => address) public oracles;

// Used to get the Current and Expected APR'S.
uint256 internal constant MAX_BPS = 10_000;
uint256 internal constant MAX_BPS_EXTENDED = 1_000_000_000_000;
uint256 internal constant SECONDS_PER_YEAR = 31_556_952;

address internal constant LEGACY_ORACLE =
0x27aD2fFc74F74Ed27e1C0A19F1858dD0963277aE;

constructor(address _governance) Governance(_governance) {}

Check warning on line 44 in src/AprOracle/AprOracle.sol

View workflow job for this annotation

GitHub Actions / solidity

Code contains empty blocks

/**
Expand All @@ -61,30 +65,44 @@ contract AprOracle is Governance {
// Get the oracle set for this specific strategy.
address oracle = oracles[_strategy];

// If not set, check the legacy oracle.
if (oracle == address(0)) {
// Do a low level call in case the legacy oracle is not deployed.
(bool success, bytes memory data) = LEGACY_ORACLE.staticcall(
abi.encodeWithSelector(
AprOracle(LEGACY_ORACLE).oracles.selector,
_strategy
)
);
if (success && data.length > 0) {
oracle = abi.decode(data, (address));
}
}

// Don't revert if a oracle is not set.
if (oracle != address(0)) {
return IOracle(oracle).aprAfterDebtChange(_strategy, _debtChange);
} else {
// If the strategy is a V3 Multi strategy vault user weighted average.
try IVault(_strategy).role_manager() returns (address) {
return getWeightedAverageApr(_strategy, _debtChange);
} catch {
// If the strategy is a v3 TokenizedStrategy, we can default to the expected apr.
try IStrategy(_strategy).fullProfitUnlockDate() returns (
uint256
) {
return getExpectedApr(_strategy, _debtChange);
} catch {
// Else just return 0.
return 0;
}
}
}
}

/**
* @notice Get the current weighted APR of a strategy.
* @dev Gives the apr weighted by its `totalAssets`. This can be used
* to get the combined expected return of a collection of strategies.
*
* @param _strategy Address of the strategy.
* @return . The current weighted APR of the strategy.
*/
function weightedApr(
address _strategy
) external view virtual returns (uint256) {
return
IStrategy(_strategy).totalAssets() * getStrategyApr(_strategy, 0);
}

/**
* @notice Set a custom APR `_oracle` for a `_strategy`.
* @dev Can only be called by the oracle's `governance` or
* @dev Can only be called by the Apr Oracle's `governance` or
* management of the `_strategy`.
*
* The `_oracle` will need to implement the IOracle interface.
Expand Down Expand Up @@ -161,4 +179,52 @@ contract AprOracle is Governance {
MAX_BPS_EXTENDED /
assets;
}

/**
* @notice Get the current weighted average APR for a V3 vault.
* @dev This is the sum of all the current APR's of the strategies in the vault.
* @param _vault The address of the vault.
* @return apr The weighted average apr expressed as 1e18.
*/
function getWeightedAverageApr(
address _vault,
int256 _delta
) public view virtual returns (uint256) {
address[] memory strategies = IVault(_vault).get_default_queue();
uint256 totalAssets = IVault(_vault).totalAssets();

uint256 totalApr = 0;
for (uint256 i = 0; i < strategies.length; i++) {
uint256 debt = IVault(_vault)
.strategies(strategies[i])
.current_debt;

if (debt == 0) continue;

// Get a performance fee if the strategy has one.
(bool success, bytes memory fee) = strategies[i].staticcall(
abi.encodeWithSelector(
IStrategy(strategies[i]).performanceFee.selector
)
);

uint256 performanceFee;
if (success) {
performanceFee = abi.decode(fee, (uint256));
}

// Get the effective debt change for the strategy.
int256 debtChange = (_delta * int256(debt)) / int256(totalAssets);

// Add the weighted apr of the strategy to the total apr.
totalApr +=
(getStrategyApr(strategies[i], debtChange) *
uint256(int256(debt) + debtChange) *
(MAX_BPS - performanceFee)) /
MAX_BPS;
}

// Divide by the total assets to get apr as 1e18.
return totalApr / uint256(int256(totalAssets) + _delta);
}
}
30 changes: 9 additions & 21 deletions src/ReportTrigger/CommonReportTrigger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ contract CommonReportTrigger is Governance {
* while still using this standard contract for keepers to read the
* trigger status from.
*
* The address calling must have the `ADD_STRATEGY_MANAGER` role on the vault.
* The address calling must have the `REPORTING_MANAGER` role on the vault.
*
* The custom trigger contract only needs to implement the `reportTrigger`
* function to return true or false.
Expand All @@ -172,13 +172,9 @@ contract CommonReportTrigger is Governance {
address _strategy,
address _trigger
) external virtual {
// Check that the address has the ADD_STRATEGY_MANAGER role on
// the vault. Just check their role has a 1 at the first position.
uint256 mask = 1;
require(
(IVault(_vault).roles(msg.sender) & mask) == mask,
"!authorized"
);
// Check that the address has the REPORTING_MANAGER role on the vault.
uint256 mask = 32;
require((IVault(_vault).roles(msg.sender) & mask) != 0, "!authorized");
customVaultTrigger[_vault][_strategy] = _trigger;

emit UpdatedCustomVaultTrigger(_vault, _strategy, _trigger);
Expand All @@ -194,7 +190,7 @@ contract CommonReportTrigger is Governance {
*
* This will have no effect if a custom trigger is set for the strategy.
*
* The address calling must have the `ADD_STRATEGY_MANAGER` role on the vault.
* The address calling must have the `REPORTING_MANAGER` role on the vault.
*
* @param _vault The address of the vault.
* @param _strategy The address of the strategy to customize.
Expand All @@ -205,13 +201,9 @@ contract CommonReportTrigger is Governance {
address _strategy,
uint256 _baseFee
) external virtual {
// Check that the address has the ADD_STRATEGY_MANAGER role on
// the vault. Just check their role has a 1 at the first position.
uint256 mask = 1;
require(
(IVault(_vault).roles(msg.sender) & mask) == mask,
"!authorized"
);
// Check that the address has the REPORTING_MANAGER role on the vault.
uint256 mask = 32;
require((IVault(_vault).roles(msg.sender) & mask) != 0, "!authorized");
customVaultBaseFee[_vault][_strategy] = _baseFee;

emit UpdatedCustomVaultBaseFee(_vault, _strategy, _baseFee);
Expand Down Expand Up @@ -432,11 +424,7 @@ contract CommonReportTrigger is Governance {
* @return . IF the current base fee is acceptable.
*/
function isCurrentBaseFeeAcceptable() external view virtual returns (bool) {
address _baseFeeProvider = baseFeeProvider;
// If no provider is set return true.
if (_baseFeeProvider == address(0)) return true;

return IBaseFee(baseFeeProvider).basefee_global() <= acceptableBaseFee;
return getCurrentBaseFee() <= acceptableBaseFee;
}

/*//////////////////////////////////////////////////////////////
Expand Down
Loading

0 comments on commit f139be6

Please sign in to comment.