From 9954386e79fd82cf059cb015f0bdd963a7643cb3 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Fri, 10 May 2024 15:49:49 +0200 Subject: [PATCH 01/20] script to update oracle --- scripts/UpdateOracle.s.sol | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 scripts/UpdateOracle.s.sol diff --git a/scripts/UpdateOracle.s.sol b/scripts/UpdateOracle.s.sol new file mode 100644 index 00000000..4daa40ab --- /dev/null +++ b/scripts/UpdateOracle.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import { Utils } from "./utils/Utils.s.sol"; +import { console } from "forge-std/console.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import "stringutils/strings.sol"; +import "./Constants.s.sol"; + +import "contracts/transmuter/Storage.sol" as Storage; +import { ITransmuter } from "interfaces/ITransmuter.sol"; +import "interfaces/external/chainlink/AggregatorV3Interface.sol"; +import "interfaces/external/IERC4626.sol"; + +import { CollateralSetupProd } from "contracts/transmuter/configs/ProductionTypes.sol"; + +contract UpdateOracle is Utils { + function run() external { + uint256 deployerPrivateKey = vm.envUint("KEEPER_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + console.log("Address: %s", deployer); + console.log(deployer.balance); + vm.startBroadcast(deployerPrivateKey); + + ITransmuter usdaTransmuter = ITransmuter(0x222222fD79264BBE280b4986F6FEfBC3524d0137); + console.log(address(usdaTransmuter)); + usdaTransmuter.updateOracle(0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB); + + /* + ITransmuter euraTransmuter = ITransmuter(0x00253582b2a3FE112feEC532221d9708c64cEFAb); + // euraTransmuter.updateOracle(0x3f95AA88dDbB7D9D484aa3D482bf0a80009c52c9); + euraTransmuter.updateOracle(0x2F123cF3F37CE3328CC9B5b8415f9EC5109b45e7); + */ + vm.stopBroadcast(); + } +} From 219520fc258eccf634f9a50f51cce10231fb3785 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Wed, 15 May 2024 08:49:33 +0200 Subject: [PATCH 02/20] feat: rebalancing script --- scripts/AdjustYieldExposure.s.sol | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 scripts/AdjustYieldExposure.s.sol diff --git a/scripts/AdjustYieldExposure.s.sol b/scripts/AdjustYieldExposure.s.sol new file mode 100644 index 00000000..564923bc --- /dev/null +++ b/scripts/AdjustYieldExposure.s.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import { Utils } from "./utils/Utils.s.sol"; +import { console } from "forge-std/console.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import "stringutils/strings.sol"; +import "./Constants.s.sol"; + +import { RebalancerFlashloan } from "contracts/helpers/RebalancerFlashloan.sol"; + +contract AdjustYieldExposure is Utils { + function run() external { + uint256 deployerPrivateKey = vm.envUint("KEEPER_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + console.log("Address: %s", deployer); + console.log(deployer.balance); + vm.startBroadcast(deployerPrivateKey); + + RebalancerFlashloan rebalancer = RebalancerFlashloan(0x22604C0E5633A9810E01c9cb469B23Eee17AC411); + rebalancer.adjustYieldExposure(1000000 * 1 ether, 1, USDC, STEAK_USDC, 900000 * 1 ether); + + vm.stopBroadcast(); + } +} From 59242726da2ca6dbdd94bb8e318e650a044ba695 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 20 May 2024 18:40:55 +0200 Subject: [PATCH 03/20] feat: stEUR deployment on Linea --- contracts/helpers/Harvester.sol | 113 +++++++++++++++++++++++++++ package.json | 3 +- scripts/AdjustYieldExposure.s.sol | 2 +- scripts/DeploySavings.s.sol | 43 ++++++---- scripts/DeploySavingsNoCreate2.s.sol | 43 ---------- scripts/vanity.json | 6 +- scripts/vanitySavingsAgEUR.json | 4 - scripts/vanitySavingsAgUSD.json | 4 - scripts/vanitySavingsEUR.json | 6 ++ scripts/vanitySavingsUSD.json | 6 ++ 10 files changed, 161 insertions(+), 69 deletions(-) create mode 100644 contracts/helpers/Harvester.sol delete mode 100644 scripts/DeploySavingsNoCreate2.s.sol delete mode 100644 scripts/vanitySavingsAgEUR.json delete mode 100644 scripts/vanitySavingsAgUSD.json create mode 100644 scripts/vanitySavingsEUR.json create mode 100644 scripts/vanitySavingsUSD.json diff --git a/contracts/helpers/Harvester.sol b/contracts/helpers/Harvester.sol new file mode 100644 index 00000000..551d9530 --- /dev/null +++ b/contracts/helpers/Harvester.sol @@ -0,0 +1,113 @@ +// // SPDX-License-Identifier: GPL-3.0 + +// pragma solidity ^0.8.19; + +// import { IERC20 } from "oz/interfaces/IERC20.sol"; +// import { SafeERC20 } from "oz/token/ERC20/utils/SafeERC20.sol"; +// import { SafeCast } from "oz/utils/math/SafeCast.sol"; + +// import { ITransmuter } from "interfaces/ITransmuter.sol"; + +// import { AccessControl, IAccessControlManager } from "../utils/AccessControl.sol"; +// import "../utils/Constants.sol"; +// import "../utils/Errors.sol"; + +// import { RebalancerFlashloan } from "./RebalancerFlashloan.sol"; + +// /// @title Rebalancer +// /// @author Angle Labs, Inc. +// /// @notice Contract built to subsidize rebalances between collateral tokens +// /// @dev This contract is meant to "wrap" the Transmuter contract and provide a way for governance to +// /// subsidize rebalances between collateral tokens. Rebalances are done through 2 swaps collateral <> agToken. +// /// @dev This contract is not meant to hold any transient funds aside from the rebalancing budget +// contract Harvester is AccessControl { +// using SafeERC20 for IERC20; +// using SafeCast for uint256; + +// /// @notice Reference to the `transmuter` implementation this contract aims at rebalancing +// ITransmuter public immutable TRANSMUTER; +// /// @notice AgToken handled by the `transmuter` of interest +// RebalancerFlashloan public rebalancer; + +// address public collateral; + +// address public vault; + +// uint64 public maxExposureYieldAsset; + +// uint64 public minExposureYieldAsset; + +// uint64 public targetExposureLiquid; + +// uint64 public maxSlippage; + +// /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// INITIALIZATION +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + +// /// @notice Initializes the immutable variables of the contract: `accessControlManager`, `transmuter` and `agToken` +// constructor(RebalancerFlashloan _rebalancer, address _collateral, address _vault) { +// ITransmuter _transmuter = _rebalancer.TRANSMUTER(); +// TRANSMUTER = _transmuter; +// rebalancer = _rebalancer; +// accessControlManager = IAccessControlManager(_transmuter.accessControlManager()); +// vault = _vault; +// collateral = _collateral; +// _getLimitExposuresYieldAsset(); +// } + +// // Due to potential transaction fees, multiple harvests may be needed to arrive at the target exposure +// function harvest() external { +// (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = TRANSMUTER.getIssuedByCollateral(collateral); +// (uint256 stablecoinsFromVault, ) = TRANSMUTER.getIssuedByCollateral(vault); +// uint8 increase; +// uint256 amount; +// if (stablecoinsFromCollateral * 1e9 > targetExposureLiquid * stablecoinsIssued) { +// // Need to increase exposure to yield bearing asset +// increase = 1; +// amount = stablecoinsFromCollateral - (targetExposureLiquid * stablecoinsIssued) / 1e9; +// if (stablecoinsFromVault + amount > (maxExposureYieldAsset * stablecoinsIssued) / 1e9) { +// amount = maxExposureYieldAsset * stablecoinsIssued - stablecoinsFromVault / 1e9; +// } +// } else { +// amount = targetExposureLiquid * stablecoinsIssued - stablecoinsFromCollateral / 1e9; +// if (amount >= stablecoinsFromVault) amount = stablecoinsFromVault; +// if (stablecoinsFromVault - amount < (minExposureYieldAsset * stablecoinsIssued) / 1e9) { +// amount = stablecoinsFromVault - (minExposureYieldAsset * stablecoinsIssued) / 1e9; +// } +// } +// TRANSMUTER.updateOracle(vault); +// rebalancer.adjustYieldExposure(amount, increase, collateral, vault, (amount * (1e9 - maxSlippage)) / 1e9); +// } + +// function setRebalancer(address _newRebalancer) external onlyGuardian { +// rebalancer = _newRebalancer; +// // Assumption is done that being built on the same Transmuter contract +// } + +// function setTargetExposure(uint64 _targetExposure) external onlyGuardian { +// targetExposureLiquid = _targetExposure; +// } + +// function setMaxSlippage(uint64 _maxSlippage) external onlyGuardian { +// maxSlippage = _maxSlippage; +// } + +// function getLimitExposuresYieldAsset() external { +// _getLimitExposuresYieldAsset(); +// } + +// function _getLimitExposuresYieldAsset() internal { +// uint64[] memory xFeeMint; +// (xFeeMint, ) = TRANSMUTER.getCollateralMintFees(vault); +// uint256 length = xFeeMint.length; +// if (length <= 1) maxExposureYieldAsset = 1e9; +// else maxExposureYieldAsset = xFeeMint[length - 2]; + +// uint64[] memory xFeeBurn; +// (xFeeBurn, ) = TRANSMUTER.getCollateralBurnFees(vault); +// length = xFeeBurn.length; +// if (length <= 1) minExposureYieldAsset = 0; +// else minExposureYieldAsset = xFeeBurn[length - 2]; +// } +// } diff --git a/package.json b/package.json index 690660c2..4c837b74 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "lint:check": "solhint --max-warnings 20 \"**/*.sol\"", "vanity": "forge script --skip test --slow -vvvv --rpc-url mainnet ./scripts/utils/VanityAddress.s.sol", "verify:agEUR": "forge verify-contract --chain-id 100 --num-of-optimizations 1000 --watch --constructor-args $(cast abi-encode 'constructor(string memory,string memory,uint8)' 'Mock-AgEUR' 'Mock-AgEUR' 18) --compiler-version v0.8.19+commit.7dd6d40 --etherscan-api-key HQU42G9VWZ6KFNYTYKE6VFB7V48KMJ69HS 0x5fE0E497Ac676d8bA78598FC8016EBC1E6cE14a3 lib/borrow-contracts/contracts/mock/MockTokenPermit:MockTokenPermit.sol", - "verify": "forge verify-contract --num-of-optimizations 1000 --watch --constructor-args 0000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 --compiler-version v0.8.19+commit.7dd6d404 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776 lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy --chain" + "verify:stUSD": "forge verify-contract --num-of-optimizations 1000 --watch --constructor-args 0000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 --compiler-version v0.8.19+commit.7dd6d404 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776 lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy --chain", + "verify:stEUR": "forge verify-contract --num-of-optimizations 1000 --watch --constructor-args 0000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 --compiler-version v0.8.19+commit.7dd6d404 0x004626A008B1aCdC4c74ab51644093b155e59A23 lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy --chain" }, "keywords": [], "author": "Angle Labs, Inc.", diff --git a/scripts/AdjustYieldExposure.s.sol b/scripts/AdjustYieldExposure.s.sol index 564923bc..d1c234db 100644 --- a/scripts/AdjustYieldExposure.s.sol +++ b/scripts/AdjustYieldExposure.s.sol @@ -18,7 +18,7 @@ contract AdjustYieldExposure is Utils { vm.startBroadcast(deployerPrivateKey); RebalancerFlashloan rebalancer = RebalancerFlashloan(0x22604C0E5633A9810E01c9cb469B23Eee17AC411); - rebalancer.adjustYieldExposure(1000000 * 1 ether, 1, USDC, STEAK_USDC, 900000 * 1 ether); + rebalancer.adjustYieldExposure(1300000 * 1 ether, 0, USDC, STEAK_USDC, 1200000 * 1 ether); vm.stopBroadcast(); } diff --git a/scripts/DeploySavings.s.sol b/scripts/DeploySavings.s.sol index 20c0328b..d4625e44 100644 --- a/scripts/DeploySavings.s.sol +++ b/scripts/DeploySavings.s.sol @@ -16,56 +16,71 @@ import { TransparentUpgradeableProxy } from "oz/proxy/transparent/TransparentUpg import { ImmutableCreate2Factory } from "./utils/TransmuterDeploymentHelper.s.sol"; /// @dev To deploy on a different chain, just replace the chainId and be sure the sdk has the required addresses +/// To match the right computed address and init code: need to deploy with Solidity 0.8.19 contract DeploySavings is Utils { using stdJson for string; using strings for *; function run() external { - // TODO: make sure that deployer has a 1 agEUR (=1e18) balance + // TODO: make sure that deployer has a 1 EURA (=1e18) balance // TODO: change the chainId uint256 chainId = CHAIN_SOURCE; uint256 deployerPrivateKey = vm.deriveKey(vm.envString("MNEMONIC_MAINNET"), "m/44'/60'/0'/0/", 0); ImmutableCreate2Factory create2Factory = ImmutableCreate2Factory(IMMUTABLE_CREATE2_FACTORY_ADDRESS); string memory jsonVanity = vm.readFile(JSON_VANITY_PATH); bytes32 salt = jsonVanity.readBytes32(string.concat("$.", "salt")); + // Setup addresses + /* + address proxyAdmin = _chainToContract(chainId, ContractType.ProxyAdmin); + address stablecoin = _chainToContract(chainId, ContractType.AgEUR); + address coreBorrow = _chainToContract(chainId, ContractType.CoreBorrow); + */ + + address proxyAdmin = 0x1D941EF0D3Bba4ad67DBfBCeE5262F4CEE53A32b; + address stablecoin = 0x1a7e4e63778B4f12a199C062f3eFdD288afCBce8; + address coreBorrow = 0x4b1E2c2762667331Bc91648052F646d1b0d35984; + console.log(proxyAdmin, stablecoin, coreBorrow); + vm.startBroadcast(deployerPrivateKey); address deployer = vm.addr(deployerPrivateKey); console.log("Deployer address: ", deployer); - // Deploying the proxy. - // To maintain chain consistency, we deploy with the deployer as a proxyAdmin before transferring - // to another address - // We use a contract that is widely deployed across many chains as an implementation to make it resilient - // to possible implementation changes - + /* bytes memory emptyData; bytes memory initCode = abi.encodePacked( type(TransparentUpgradeableProxy).creationCode, abi.encode(IMMUTABLE_CREATE2_FACTORY_ADDRESS, deployer, emptyData) ); + */ + // initCode on stEUR + initCode = hex"60406080815262000cdb80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a3575016179055516108869081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe60806040526004361015610019575b3661037c575b61037c565b6000803560e01c9081633659cfe61461006c575080634f1ef286146100675780635c60da1b146100625780638f2839701461005d5763f851a4400361000e57610326565b610212565b61019e565b6100ef565b346100d15760203660031901126100d1576100856100d4565b6001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014576100ce906100c56104af565b90838252610516565b80f35b80fd5b600435906001600160a01b03821682036100ea57565b600080fd5b60403660031901126100ea576101036100d4565b60243567ffffffffffffffff918282116100ea57366023830112156100ea5781600401359283116100ea5736602484840101116100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461001457600060208480602461018a61018561019c996104fa565b6104d4565b96828852018387013784010152610624565b005b346100ea5760003660031901126100ea576001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460405191168152602090f35b346100ea5760203660031901126100ea5761022b6100d4565b6001600160a01b03907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610014577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156102bc5773ffffffffffffffffffffffffffffffffffffffff1916179055005b608460405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b346100ea5760003660031901126100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039080825416331460001461001457905460405191168152602090f35b6001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146103f0577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e156103ec573d90f35b3d90fd5b60a460405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff8211176104cf57604052565b610480565b6040519190601f01601f1916820167ffffffffffffffff8111838210176104cf57604052565b67ffffffffffffffff81116104cf57601f01601f191660200190565b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906105b2575b61059d575050565b6105af916105a96106b2565b91610722565b50565b506000610595565b608460405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906106aa5761059d575050565b506001610595565b604051906060820182811067ffffffffffffffff8211176104cf57604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b9190823b1561076b576000816107609460208394519201905af43d15610763573d90610750610185836104fa565b9182523d6000602084013e6107d5565b90565b6060906107d5565b608460405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b909190156107e1575090565b8151156107f15750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510610837575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061081456fea2646970667358221220cd51d87f687e65d41171d5f157313805c8c9f1c12984f1d6b0d726a20f3df98e64736f6c634300081300330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"; + console.log("Proxy bytecode"); console.logBytes(initCode); console.log(""); - /* + address computedAddress = create2Factory.findCreate2Address(salt, initCode); console.log("Supposed to deploy: %s", computedAddress); + if (computedAddress != 0x004626A008B1aCdC4c74ab51644093b155e59A23) revert(); + address saving = create2Factory.safeCreate2(salt, initCode); console.log("Savings deployed at: ", address(saving)); // Deploying the implementation + Savings savingsImpl = new Savings(); TransparentUpgradeableProxy(payable(saving)).upgradeTo(address(savingsImpl)); - TransparentUpgradeableProxy(payable(saving)).changeAdmin(_chainToContract(chainId, ContractType.ProxyAdmin)); - IERC20MetadataUpgradeable(_chainToContract(chainId, ContractType.AgEUR)).approve(address(saving), 1e18); + TransparentUpgradeableProxy(payable(saving)).changeAdmin(proxyAdmin); + IERC20MetadataUpgradeable(stablecoin).approve(address(saving), 1e18); Savings(saving).initialize( - IAccessControlManager(_chainToContract(chainId, ContractType.CoreBorrow)), - IERC20MetadataUpgradeable(_chainToContract(chainId, ContractType.AgEUR)), - "Staked agEUR", + IAccessControlManager(coreBorrow), + IERC20MetadataUpgradeable(stablecoin), + "Staked EURA", "stEUR", 1 ); - */ + vm.stopBroadcast(); } } diff --git a/scripts/DeploySavingsNoCreate2.s.sol b/scripts/DeploySavingsNoCreate2.s.sol deleted file mode 100644 index 2521cca2..00000000 --- a/scripts/DeploySavingsNoCreate2.s.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; - -import "./utils/Utils.s.sol"; -import { console } from "forge-std/console.sol"; -import { Savings } from "contracts/savings/Savings.sol"; -import { CHAIN_SOURCE } from "./Constants.s.sol"; -import { IAccessControlManager } from "contracts/utils/AccessControl.sol"; -import "./Constants.s.sol"; -import "oz/interfaces/IERC20.sol"; -import "oz-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; - -/// @dev To deploy on a different chain, just replace the chainId and be sure the sdk has the required addresses -/// @dev This is a vanilla deployment file to easily and rapidly deploy a savings implementation at a random address -contract DeploySavingsNoCreate2 is Utils { - function run() external { - // TODO: make sure that deployer has a 1 agEUR (=1e18) balance - // TODO: change the chainId - uint256 chainId = CHAIN_SOURCE; - uint256 deployerPrivateKey = vm.deriveKey(vm.envString("MNEMONIC_FORK"), "m/44'/60'/0'/0/", 0); - vm.startBroadcast(deployerPrivateKey); - - address deployer = vm.addr(deployerPrivateKey); - console.log("Deployer address: ", deployer); - - Savings savingsImpl = new Savings(); - bytes memory emptyData; - Savings saving = Savings( - _deployUpgradeable(address(savingsImpl), _chainToContract(chainId, ContractType.ProxyAdmin), emptyData) - ); - console.log("Savings deployed at: ", address(saving)); - IERC20MetadataUpgradeable(_chainToContract(chainId, ContractType.AgEUR)).approve(address(saving), 1e18); - saving.initialize( - IAccessControlManager(_chainToContract(chainId, ContractType.CoreBorrow)), - IERC20MetadataUpgradeable(_chainToContract(chainId, ContractType.AgEUR)), - "agEUR Savings Account", - "sagEUR", - 1 - ); - - vm.stopBroadcast(); - } -} diff --git a/scripts/vanity.json b/scripts/vanity.json index d7361e9c..fb461e6d 100644 --- a/scripts/vanity.json +++ b/scripts/vanity.json @@ -1,4 +1,6 @@ { - "init": 16930517, - "salt": "0xfda462548ce04282f4b6d6619823a7c64fdc01850000000000000000010256d5" + "init": 4427456, + "salt": "0xfda462548ce04282f4b6d6619823a7c64fdc0185000000000000000000438ec0", + "vanityAddress": "0x004626A008B1aCdC4c74ab51644093b155e59A23", + "initCode": "0x60406080815262000cdb80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a3575016179055516108869081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe60806040526004361015610019575b3661037c575b61037c565b6000803560e01c9081633659cfe61461006c575080634f1ef286146100675780635c60da1b146100625780638f2839701461005d5763f851a4400361000e57610326565b610212565b61019e565b6100ef565b346100d15760203660031901126100d1576100856100d4565b6001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014576100ce906100c56104af565b90838252610516565b80f35b80fd5b600435906001600160a01b03821682036100ea57565b600080fd5b60403660031901126100ea576101036100d4565b60243567ffffffffffffffff918282116100ea57366023830112156100ea5781600401359283116100ea5736602484840101116100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461001457600060208480602461018a61018561019c996104fa565b6104d4565b96828852018387013784010152610624565b005b346100ea5760003660031901126100ea576001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460405191168152602090f35b346100ea5760203660031901126100ea5761022b6100d4565b6001600160a01b03907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610014577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156102bc5773ffffffffffffffffffffffffffffffffffffffff1916179055005b608460405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b346100ea5760003660031901126100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039080825416331460001461001457905460405191168152602090f35b6001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146103f0577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e156103ec573d90f35b3d90fd5b60a460405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff8211176104cf57604052565b610480565b6040519190601f01601f1916820167ffffffffffffffff8111838210176104cf57604052565b67ffffffffffffffff81116104cf57601f01601f191660200190565b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906105b2575b61059d575050565b6105af916105a96106b2565b91610722565b50565b506000610595565b608460405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906106aa5761059d575050565b506001610595565b604051906060820182811067ffffffffffffffff8211176104cf57604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b9190823b1561076b576000816107609460208394519201905af43d15610763573d90610750610185836104fa565b9182523d6000602084013e6107d5565b90565b6060906107d5565b608460405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b909190156107e1575090565b8151156107f15750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510610837575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061081456fea2646970667358221220cd51d87f687e65d41171d5f157313805c8c9f1c12984f1d6b0d726a20f3df98e64736f6c634300081300330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" } diff --git a/scripts/vanitySavingsAgEUR.json b/scripts/vanitySavingsAgEUR.json deleted file mode 100644 index ae23208c..00000000 --- a/scripts/vanitySavingsAgEUR.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "init": 4427456, - "salt": "0xfda462548ce04282f4b6d6619823a7c64fdc0185000000000000000000438ec0" -} diff --git a/scripts/vanitySavingsAgUSD.json b/scripts/vanitySavingsAgUSD.json deleted file mode 100644 index caf2f0a6..00000000 --- a/scripts/vanitySavingsAgUSD.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "init": 16930517, - "salt": "0xfda462548ce04282f4b6d6619823a7c64fdc01850000000000000000010256d5" -} \ No newline at end of file diff --git a/scripts/vanitySavingsEUR.json b/scripts/vanitySavingsEUR.json new file mode 100644 index 00000000..fb461e6d --- /dev/null +++ b/scripts/vanitySavingsEUR.json @@ -0,0 +1,6 @@ +{ + "init": 4427456, + "salt": "0xfda462548ce04282f4b6d6619823a7c64fdc0185000000000000000000438ec0", + "vanityAddress": "0x004626A008B1aCdC4c74ab51644093b155e59A23", + "initCode": "0x60406080815262000cdb80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a3575016179055516108869081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe60806040526004361015610019575b3661037c575b61037c565b6000803560e01c9081633659cfe61461006c575080634f1ef286146100675780635c60da1b146100625780638f2839701461005d5763f851a4400361000e57610326565b610212565b61019e565b6100ef565b346100d15760203660031901126100d1576100856100d4565b6001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014576100ce906100c56104af565b90838252610516565b80f35b80fd5b600435906001600160a01b03821682036100ea57565b600080fd5b60403660031901126100ea576101036100d4565b60243567ffffffffffffffff918282116100ea57366023830112156100ea5781600401359283116100ea5736602484840101116100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461001457600060208480602461018a61018561019c996104fa565b6104d4565b96828852018387013784010152610624565b005b346100ea5760003660031901126100ea576001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460405191168152602090f35b346100ea5760203660031901126100ea5761022b6100d4565b6001600160a01b03907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610014577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156102bc5773ffffffffffffffffffffffffffffffffffffffff1916179055005b608460405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b346100ea5760003660031901126100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039080825416331460001461001457905460405191168152602090f35b6001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146103f0577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e156103ec573d90f35b3d90fd5b60a460405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff8211176104cf57604052565b610480565b6040519190601f01601f1916820167ffffffffffffffff8111838210176104cf57604052565b67ffffffffffffffff81116104cf57601f01601f191660200190565b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906105b2575b61059d575050565b6105af916105a96106b2565b91610722565b50565b506000610595565b608460405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906106aa5761059d575050565b506001610595565b604051906060820182811067ffffffffffffffff8211176104cf57604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b9190823b1561076b576000816107609460208394519201905af43d15610763573d90610750610185836104fa565b9182523d6000602084013e6107d5565b90565b6060906107d5565b608460405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b909190156107e1575090565b8151156107f15750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510610837575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061081456fea2646970667358221220cd51d87f687e65d41171d5f157313805c8c9f1c12984f1d6b0d726a20f3df98e64736f6c634300081300330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/scripts/vanitySavingsUSD.json b/scripts/vanitySavingsUSD.json new file mode 100644 index 00000000..c9d7973b --- /dev/null +++ b/scripts/vanitySavingsUSD.json @@ -0,0 +1,6 @@ +{ + "init": 16930517, + "salt": "0xfda462548ce04282f4b6d6619823a7c64fdc01850000000000000000010256d5", + "vanityAddress": "0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776", + "initCode": "0x60406080815262000cdb80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a3575016179055516108869081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe60806040526004361015610019575b3661037c575b61037c565b6000803560e01c9081633659cfe61461006c575080634f1ef286146100675780635c60da1b146100625780638f2839701461005d5763f851a4400361000e57610326565b610212565b61019e565b6100ef565b346100d15760203660031901126100d1576100856100d4565b6001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014576100ce906100c56104af565b90838252610516565b80f35b80fd5b600435906001600160a01b03821682036100ea57565b600080fd5b60403660031901126100ea576101036100d4565b60243567ffffffffffffffff918282116100ea57366023830112156100ea5781600401359283116100ea5736602484840101116100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461001457600060208480602461018a61018561019c996104fa565b6104d4565b96828852018387013784010152610624565b005b346100ea5760003660031901126100ea576001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460405191168152602090f35b346100ea5760203660031901126100ea5761022b6100d4565b6001600160a01b03907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610014577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156102bc5773ffffffffffffffffffffffffffffffffffffffff1916179055005b608460405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b346100ea5760003660031901126100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039080825416331460001461001457905460405191168152602090f35b6001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146103f0577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e156103ec573d90f35b3d90fd5b60a460405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff8211176104cf57604052565b610480565b6040519190601f01601f1916820167ffffffffffffffff8111838210176104cf57604052565b67ffffffffffffffff81116104cf57601f01601f191660200190565b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906105b2575b61059d575050565b6105af916105a96106b2565b91610722565b50565b506000610595565b608460405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906106aa5761059d575050565b506001610595565b604051906060820182811067ffffffffffffffff8211176104cf57604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b9190823b1561076b576000816107609460208394519201905af43d15610763573d90610750610185836104fa565b9182523d6000602084013e6107d5565b90565b6060906107d5565b608460405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b909190156107e1575090565b8151156107f15750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510610837575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061081456fea2646970667358221220d55b5da8b924769f7b438faccab9195abc467b47fdb6d935592c5612e7e7412164736f6c634300081300330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" +} From fcee88d5cbd5514793b4782dd5459a22c47d6ed2 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Wed, 22 May 2024 20:27:25 +0200 Subject: [PATCH 04/20] rebalancing contract --- contracts/helpers/Harvester.sol | 290 +++++++++++++++++++------------- scripts/DeploySavings.s.sol | 3 +- 2 files changed, 179 insertions(+), 114 deletions(-) diff --git a/contracts/helpers/Harvester.sol b/contracts/helpers/Harvester.sol index 551d9530..a47a5b42 100644 --- a/contracts/helpers/Harvester.sol +++ b/contracts/helpers/Harvester.sol @@ -1,113 +1,177 @@ -// // SPDX-License-Identifier: GPL-3.0 - -// pragma solidity ^0.8.19; - -// import { IERC20 } from "oz/interfaces/IERC20.sol"; -// import { SafeERC20 } from "oz/token/ERC20/utils/SafeERC20.sol"; -// import { SafeCast } from "oz/utils/math/SafeCast.sol"; - -// import { ITransmuter } from "interfaces/ITransmuter.sol"; - -// import { AccessControl, IAccessControlManager } from "../utils/AccessControl.sol"; -// import "../utils/Constants.sol"; -// import "../utils/Errors.sol"; - -// import { RebalancerFlashloan } from "./RebalancerFlashloan.sol"; - -// /// @title Rebalancer -// /// @author Angle Labs, Inc. -// /// @notice Contract built to subsidize rebalances between collateral tokens -// /// @dev This contract is meant to "wrap" the Transmuter contract and provide a way for governance to -// /// subsidize rebalances between collateral tokens. Rebalances are done through 2 swaps collateral <> agToken. -// /// @dev This contract is not meant to hold any transient funds aside from the rebalancing budget -// contract Harvester is AccessControl { -// using SafeERC20 for IERC20; -// using SafeCast for uint256; - -// /// @notice Reference to the `transmuter` implementation this contract aims at rebalancing -// ITransmuter public immutable TRANSMUTER; -// /// @notice AgToken handled by the `transmuter` of interest -// RebalancerFlashloan public rebalancer; - -// address public collateral; - -// address public vault; - -// uint64 public maxExposureYieldAsset; - -// uint64 public minExposureYieldAsset; - -// uint64 public targetExposureLiquid; - -// uint64 public maxSlippage; - -// /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// INITIALIZATION -// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ - -// /// @notice Initializes the immutable variables of the contract: `accessControlManager`, `transmuter` and `agToken` -// constructor(RebalancerFlashloan _rebalancer, address _collateral, address _vault) { -// ITransmuter _transmuter = _rebalancer.TRANSMUTER(); -// TRANSMUTER = _transmuter; -// rebalancer = _rebalancer; -// accessControlManager = IAccessControlManager(_transmuter.accessControlManager()); -// vault = _vault; -// collateral = _collateral; -// _getLimitExposuresYieldAsset(); -// } - -// // Due to potential transaction fees, multiple harvests may be needed to arrive at the target exposure -// function harvest() external { -// (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = TRANSMUTER.getIssuedByCollateral(collateral); -// (uint256 stablecoinsFromVault, ) = TRANSMUTER.getIssuedByCollateral(vault); -// uint8 increase; -// uint256 amount; -// if (stablecoinsFromCollateral * 1e9 > targetExposureLiquid * stablecoinsIssued) { -// // Need to increase exposure to yield bearing asset -// increase = 1; -// amount = stablecoinsFromCollateral - (targetExposureLiquid * stablecoinsIssued) / 1e9; -// if (stablecoinsFromVault + amount > (maxExposureYieldAsset * stablecoinsIssued) / 1e9) { -// amount = maxExposureYieldAsset * stablecoinsIssued - stablecoinsFromVault / 1e9; -// } -// } else { -// amount = targetExposureLiquid * stablecoinsIssued - stablecoinsFromCollateral / 1e9; -// if (amount >= stablecoinsFromVault) amount = stablecoinsFromVault; -// if (stablecoinsFromVault - amount < (minExposureYieldAsset * stablecoinsIssued) / 1e9) { -// amount = stablecoinsFromVault - (minExposureYieldAsset * stablecoinsIssued) / 1e9; -// } -// } -// TRANSMUTER.updateOracle(vault); -// rebalancer.adjustYieldExposure(amount, increase, collateral, vault, (amount * (1e9 - maxSlippage)) / 1e9); -// } - -// function setRebalancer(address _newRebalancer) external onlyGuardian { -// rebalancer = _newRebalancer; -// // Assumption is done that being built on the same Transmuter contract -// } - -// function setTargetExposure(uint64 _targetExposure) external onlyGuardian { -// targetExposureLiquid = _targetExposure; -// } - -// function setMaxSlippage(uint64 _maxSlippage) external onlyGuardian { -// maxSlippage = _maxSlippage; -// } - -// function getLimitExposuresYieldAsset() external { -// _getLimitExposuresYieldAsset(); -// } - -// function _getLimitExposuresYieldAsset() internal { -// uint64[] memory xFeeMint; -// (xFeeMint, ) = TRANSMUTER.getCollateralMintFees(vault); -// uint256 length = xFeeMint.length; -// if (length <= 1) maxExposureYieldAsset = 1e9; -// else maxExposureYieldAsset = xFeeMint[length - 2]; - -// uint64[] memory xFeeBurn; -// (xFeeBurn, ) = TRANSMUTER.getCollateralBurnFees(vault); -// length = xFeeBurn.length; -// if (length <= 1) minExposureYieldAsset = 0; -// else minExposureYieldAsset = xFeeBurn[length - 2]; -// } -// } +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.19; + +import { IERC20 } from "oz/interfaces/IERC20.sol"; +import { IERC4626 } from "interfaces/external/IERC4626.sol"; +import { SafeERC20 } from "oz/token/ERC20/utils/SafeERC20.sol"; +import { SafeCast } from "oz/utils/math/SafeCast.sol"; + +import { ITransmuter } from "interfaces/ITransmuter.sol"; + +import { AccessControl, IAccessControlManager } from "../utils/AccessControl.sol"; +import "../utils/Constants.sol"; +import "../utils/Errors.sol"; + +import { RebalancerFlashloan } from "./RebalancerFlashloan.sol"; + +/// @title Harvester +/// @author Angle Labs, Inc. +/// @dev Contract for anyone to permissionlessly adjust the reserves of Angle Transmuter through the RebalancerFlashloan contract +contract Harvester is AccessControl { + using SafeERC20 for IERC20; + using SafeCast for uint256; + + struct CollateralSetup { + // Vault associated to the collateral + address vault; + // Target exposure to the collateral asset used in the vault + uint64 targetExposure; + // Maximum exposure within the Transmuter to the vault asset + uint64 maxExposureYieldAsset; + // Minimum exposure within the Transmuter to the vault asset + uint64 minExposureYieldAsset; + // Whether limit exposures should be overriden or read onchain through the Transmuter + uint64 overrideExposures; + } + + /// @notice Reference to the `transmuter` implementation this contract aims at rebalancing + ITransmuter public immutable TRANSMUTER; + /// @notice Permissioned rebalancer contract + RebalancerFlashloan public rebalancer; + /// @notice Max slippage when dealing with the Transmuter + uint96 public maxSlippage; + /// @notice Data associated to a collateral + mapping(address => CollateralSetup) public collateralData; + + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + INITIALIZATION + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + + constructor( + RebalancerFlashloan _rebalancer, + address vault, + uint64 targetExposure, + uint64 overrideExposures, + uint64 maxExposureYieldAsset, + uint64 minExposureYieldAsset + ) { + ITransmuter transmuter = _rebalancer.TRANSMUTER(); + TRANSMUTER = transmuter; + rebalancer = _rebalancer; + accessControlManager = IAccessControlManager(transmuter.accessControlManager()); + _setCollateralData(vault, targetExposure, minExposureYieldAsset, maxExposureYieldAsset, overrideExposures); + } + + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + HARVEST + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + + /// @notice Invests or divests from the yield asset associated to `collateral` + /// @dev This transaction either reduces the exposure to `collateral` in the Transmuter or frees up some collateral + /// that can then be used for people looking to burn + /// @dev Due to potential transaction fees within the Transmuter, this function doesn't exactly bring `collateral` + /// to the target exposure + function harvest(address collateral) external { + (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = TRANSMUTER.getIssuedByCollateral(collateral); + CollateralSetup memory collatInfo = collateralData[collateral]; + (uint256 stablecoinsFromVault, ) = TRANSMUTER.getIssuedByCollateral(collatInfo.vault); + uint8 increase; + uint256 amount; + uint256 targetExposureLiquid = collatInfo.targetExposure; + if (stablecoinsFromCollateral * 1e9 > targetExposureLiquid * stablecoinsIssued) { + // Need to increase exposure to yield bearing asset + increase = 1; + amount = stablecoinsFromCollateral - (targetExposureLiquid * stablecoinsIssued) / 1e9; + uint256 maxValueScaled = collatInfo.maxExposureYieldAsset * stablecoinsIssued; + // These checks assume that there are no transaction fees on the stablecoin->collateral conversion and so + // it's still possible that exposure goes above the max exposure in some rare cases + if (stablecoinsFromVault * 1e9 > maxValueScaled) amount = 0; + else if ((stablecoinsFromVault + amount) * 1e9 > maxValueScaled) + amount = maxValueScaled / 1e9 - stablecoinsFromVault; + } else { + // In this case, exposure after the operation might remain slightly below the targetExposure as less + // collateral may be obtained by burning stablecoins for the yield asset and unwrapping it + amount = (targetExposureLiquid * stablecoinsIssued) / 1e9 - stablecoinsFromCollateral; + uint256 minValueScaled = collatInfo.minExposureYieldAsset * stablecoinsIssued; + if (stablecoinsFromVault * 1e9 < minValueScaled) amount = 0; + else if (stablecoinsFromVault * 1e9 < minValueScaled + amount * 1e9) + amount = stablecoinsFromVault - minValueScaled / 1e9; + } + if (amount > 0) { + TRANSMUTER.updateOracle(collatInfo.vault); + rebalancer.adjustYieldExposure( + amount, + increase, + collateral, + collatInfo.vault, + (amount * (1e9 - maxSlippage)) / 1e9 + ); + } + } + + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + + function setRebalancer(address _newRebalancer) external onlyGuardian { + if (_newRebalancer == address(0)) revert ZeroAddress(); + rebalancer = RebalancerFlashloan(_newRebalancer); + } + + function setCollateralData( + address vault, + uint64 targetExposure, + uint64 minExposureYieldAsset, + uint64 maxExposureYieldAsset, + uint64 overrideExposures + ) external onlyGuardian { + _setCollateralData(vault, targetExposure, minExposureYieldAsset, maxExposureYieldAsset, overrideExposures); + } + + function setMaxSlippage(uint96 _maxSlippage) external onlyGuardian { + if (maxSlippage > 1e9) revert InvalidParam(); + maxSlippage = _maxSlippage; + } + + function updateLimitExposuresYieldAsset(address collateral) external { + CollateralSetup storage collatInfo = collateralData[collateral]; + if (collatInfo.overrideExposures == 0) _updateLimitExposuresYieldAsset(collatInfo); + } + + function _setCollateralData( + address vault, + uint64 targetExposure, + uint64 minExposureYieldAsset, + uint64 maxExposureYieldAsset, + uint64 overrideExposures + ) internal { + address collateral = address(IERC4626(vault).asset()); + CollateralSetup storage collatInfo = collateralData[collateral]; + collatInfo.vault = vault; + if (targetExposure >= 1e9) revert InvalidParam(); + collatInfo.targetExposure = targetExposure; + collatInfo.overrideExposures = overrideExposures; + if (overrideExposures == 1) { + if (maxExposureYieldAsset >= 1e9 || minExposureYieldAsset >= maxExposureYieldAsset) revert InvalidParam(); + collatInfo.maxExposureYieldAsset = maxExposureYieldAsset; + collatInfo.minExposureYieldAsset = minExposureYieldAsset; + } else { + _updateLimitExposuresYieldAsset(collatInfo); + } + } + + function _updateLimitExposuresYieldAsset(CollateralSetup storage collatInfo) internal { + uint64[] memory xFeeMint; + (xFeeMint, ) = TRANSMUTER.getCollateralMintFees(collatInfo.vault); + uint256 length = xFeeMint.length; + if (length <= 1) collatInfo.maxExposureYieldAsset = 1e9; + else collatInfo.maxExposureYieldAsset = xFeeMint[length - 2]; + + uint64[] memory xFeeBurn; + (xFeeBurn, ) = TRANSMUTER.getCollateralBurnFees(collatInfo.vault); + length = xFeeBurn.length; + if (length <= 1) collatInfo.minExposureYieldAsset = 0; + else collatInfo.minExposureYieldAsset = xFeeBurn[length - 2]; + } +} diff --git a/scripts/DeploySavings.s.sol b/scripts/DeploySavings.s.sol index d4625e44..f2f361f4 100644 --- a/scripts/DeploySavings.s.sol +++ b/scripts/DeploySavings.s.sol @@ -54,7 +54,8 @@ contract DeploySavings is Utils { ); */ // initCode on stEUR - initCode = hex"60406080815262000cdb80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a3575016179055516108869081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe60806040526004361015610019575b3661037c575b61037c565b6000803560e01c9081633659cfe61461006c575080634f1ef286146100675780635c60da1b146100625780638f2839701461005d5763f851a4400361000e57610326565b610212565b61019e565b6100ef565b346100d15760203660031901126100d1576100856100d4565b6001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014576100ce906100c56104af565b90838252610516565b80f35b80fd5b600435906001600160a01b03821682036100ea57565b600080fd5b60403660031901126100ea576101036100d4565b60243567ffffffffffffffff918282116100ea57366023830112156100ea5781600401359283116100ea5736602484840101116100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461001457600060208480602461018a61018561019c996104fa565b6104d4565b96828852018387013784010152610624565b005b346100ea5760003660031901126100ea576001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460405191168152602090f35b346100ea5760203660031901126100ea5761022b6100d4565b6001600160a01b03907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610014577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156102bc5773ffffffffffffffffffffffffffffffffffffffff1916179055005b608460405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b346100ea5760003660031901126100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039080825416331460001461001457905460405191168152602090f35b6001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146103f0577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e156103ec573d90f35b3d90fd5b60a460405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff8211176104cf57604052565b610480565b6040519190601f01601f1916820167ffffffffffffffff8111838210176104cf57604052565b67ffffffffffffffff81116104cf57601f01601f191660200190565b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906105b2575b61059d575050565b6105af916105a96106b2565b91610722565b50565b506000610595565b608460405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906106aa5761059d575050565b506001610595565b604051906060820182811067ffffffffffffffff8211176104cf57604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b9190823b1561076b576000816107609460208394519201905af43d15610763573d90610750610185836104fa565b9182523d6000602084013e6107d5565b90565b6060906107d5565b608460405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b909190156107e1575090565b8151156107f15750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510610837575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061081456fea2646970667358221220cd51d87f687e65d41171d5f157313805c8c9f1c12984f1d6b0d726a20f3df98e64736f6c634300081300330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"; + bytes + memory initCode = hex"60406080815262000cdb80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a3575016179055516108869081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe60806040526004361015610019575b3661037c575b61037c565b6000803560e01c9081633659cfe61461006c575080634f1ef286146100675780635c60da1b146100625780638f2839701461005d5763f851a4400361000e57610326565b610212565b61019e565b6100ef565b346100d15760203660031901126100d1576100856100d4565b6001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014576100ce906100c56104af565b90838252610516565b80f35b80fd5b600435906001600160a01b03821682036100ea57565b600080fd5b60403660031901126100ea576101036100d4565b60243567ffffffffffffffff918282116100ea57366023830112156100ea5781600401359283116100ea5736602484840101116100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461001457600060208480602461018a61018561019c996104fa565b6104d4565b96828852018387013784010152610624565b005b346100ea5760003660031901126100ea576001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460405191168152602090f35b346100ea5760203660031901126100ea5761022b6100d4565b6001600160a01b03907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610014577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156102bc5773ffffffffffffffffffffffffffffffffffffffff1916179055005b608460405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b346100ea5760003660031901126100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039080825416331460001461001457905460405191168152602090f35b6001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146103f0577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e156103ec573d90f35b3d90fd5b60a460405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff8211176104cf57604052565b610480565b6040519190601f01601f1916820167ffffffffffffffff8111838210176104cf57604052565b67ffffffffffffffff81116104cf57601f01601f191660200190565b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906105b2575b61059d575050565b6105af916105a96106b2565b91610722565b50565b506000610595565b608460405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906106aa5761059d575050565b506001610595565b604051906060820182811067ffffffffffffffff8211176104cf57604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b9190823b1561076b576000816107609460208394519201905af43d15610763573d90610750610185836104fa565b9182523d6000602084013e6107d5565b90565b6060906107d5565b608460405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b909190156107e1575090565b8151156107f15750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510610837575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061081456fea2646970667358221220cd51d87f687e65d41171d5f157313805c8c9f1c12984f1d6b0d726a20f3df98e64736f6c634300081300330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"; console.log("Proxy bytecode"); console.logBytes(initCode); From 45e573d78a95ca079014bee9f049b7a5a81b59eb Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Thu, 23 May 2024 17:33:36 +0200 Subject: [PATCH 05/20] feat: tests for the upgradeable savings contract --- contracts/savings/Savings.sol | 27 +++- .../savings/nameable/SavingsNameable.sol | 35 ++++++ scripts/DeploySavingsNameable.sol | 21 ++++ test/fuzz/SavingsTest.t.sol | 71 ++++++++++- test/units/upgrade/SavingsNameable.t.sol | 115 ++++++++++++++++++ 5 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 contracts/savings/nameable/SavingsNameable.sol create mode 100644 scripts/DeploySavingsNameable.sol create mode 100644 test/units/upgrade/SavingsNameable.t.sol diff --git a/contracts/savings/Savings.sol b/contracts/savings/Savings.sol index efdc04e6..c15d19d5 100644 --- a/contracts/savings/Savings.sol +++ b/contracts/savings/Savings.sol @@ -29,6 +29,9 @@ contract Savings is BaseSavings { /// to a level inferior to the current rate uint256 public maxRate; + /// @notice Checks whether the address is trusted to set the rate + mapping(address => uint256) public isTrustedUpdater; + uint256[49] private __gap; /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -38,6 +41,7 @@ contract Savings is BaseSavings { event Accrued(uint256 interest); event MaxRateUpdated(uint256 newMaxRate); event ToggledPause(uint128 pauseStatus); + event ToggledTrusted(address indexed trustedAddress, uint256 trustedStatus); event RateUpdated(uint256 newRate); /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -64,6 +68,7 @@ contract Savings is BaseSavings { if (address(_accessControlManager) == address(0)) revert ZeroAddress(); __ERC4626_init(asset_); __ERC20_init(name_, symbol_); + _setNameAndSymbol(name_, symbol_); accessControlManager = _accessControlManager; _deposit(msg.sender, address(this), 10 ** (asset_.decimals()) / divizer, BASE_18 / divizer); } @@ -78,6 +83,13 @@ contract Savings is BaseSavings { _; } + /// @notice Checks whether the sender is allowed to update the rate + modifier onlyTrustedOrGuardian() { + if (isTrustedUpdater[msg.sender] == 0 && !accessControlManager.isGovernorOrGuardian(msg.sender)) + revert NotTrusted(); + _; + } + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CONTRACT LOGIC //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ @@ -203,6 +215,8 @@ contract Savings is BaseSavings { : shares.mulDiv(newTotalAssets, supply, rounding); } + function _setNameAndSymbol(string memory newName, string memory newSymbol) internal virtual {} + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// HELPERS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ @@ -229,10 +243,17 @@ contract Savings is BaseSavings { emit ToggledPause(pauseStatus); } + /// @notice Toggles an address + function toggleTrusted(address trustedAddress) external onlyGuardian { + uint256 trustedStatus = 1 - isTrustedUpdater[trustedAddress]; + isTrustedUpdater[trustedAddress] = trustedStatus; + emit ToggledTrusted(trustedAddress, trustedStatus); + } + /// @notice Updates the inflation rate for depositing `asset` in this contract - /// @dev Any `rate` can be set by the guardian provided that it is inferior to the `maxRate` settable - /// by a governor address - function setRate(uint208 newRate) external onlyGuardian { + /// @dev Any `rate` can be set by the guardian or by a trusted address provided that it is inferior to + ///the `maxRate` settable by a governor address + function setRate(uint208 newRate) external onlyTrustedOrGuardian { if (newRate > maxRate) revert InvalidRate(); _accrue(); rate = newRate; diff --git a/contracts/savings/nameable/SavingsNameable.sol b/contracts/savings/nameable/SavingsNameable.sol new file mode 100644 index 00000000..f594db2f --- /dev/null +++ b/contracts/savings/nameable/SavingsNameable.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.19; + +import "../Savings.sol"; + +/// @title SavingsNameable +/// @author Angle Labs, Inc. +contract SavingsNameable is Savings { + string internal __name; + + string internal __symbol; + + uint256[48] private __gapNameable; + + /// @inheritdoc ERC20Upgradeable + function name() public view override(ERC20Upgradeable, IERC20MetadataUpgradeable) returns (string memory) { + return __name; + } + + /// @inheritdoc ERC20Upgradeable + function symbol() public view override(ERC20Upgradeable, IERC20MetadataUpgradeable) returns (string memory) { + return __symbol; + } + + /// @notice Updates the name and symbol of the token + function setNameAndSymbol(string memory newName, string memory newSymbol) external onlyGovernor { + _setNameAndSymbol(newName, newSymbol); + } + + function _setNameAndSymbol(string memory newName, string memory newSymbol) internal override { + __name = newName; + __symbol = newSymbol; + } +} diff --git a/scripts/DeploySavingsNameable.sol b/scripts/DeploySavingsNameable.sol new file mode 100644 index 00000000..3ba7b5cb --- /dev/null +++ b/scripts/DeploySavingsNameable.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import "./utils/Utils.s.sol"; +import "utils/src/Constants.sol"; +import { console } from "forge-std/console.sol"; +import "oz/interfaces/IERC20.sol"; +import { SavingsNameable } from "contracts/savings/nameable/SavingsNameable.sol"; + +/// @dev To deploy on a different chain, just replace the chainId and be sure the sdk has the required addresses +contract DeploySavingsNameable is Utils { + function run() external { + uint256 privateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + vm.startBroadcast(privateKey); + address executor = vm.addr(privateKey); + vm.label(executor, "Executor"); + SavingsNameable savingsImpl = new SavingsNameable(); + console.log("New Savings Implementation deployed at: ", address(savingsImpl)); + vm.stopBroadcast(); + } +} diff --git a/test/fuzz/SavingsTest.t.sol b/test/fuzz/SavingsTest.t.sol index 1097f0c8..e208ccde 100644 --- a/test/fuzz/SavingsTest.t.sol +++ b/test/fuzz/SavingsTest.t.sol @@ -16,6 +16,7 @@ contract SavingsTest is Fixture, FunctionUtils { event MaxRateUpdated(uint256 newMaxRate); event RateUpdated(uint256 newRate); + event ToggledTrusted(address indexed trustedAddress, uint256 trustedStatus); uint256 internal constant _initDeposit = 1e12; uint256 internal constant _minAmount = 10 ** 10; @@ -157,6 +158,28 @@ contract SavingsTest is Fixture, FunctionUtils { vm.stopPrank(); } + function test_ToggleTrusted() public { + vm.expectRevert(Errors.NotGovernorOrGuardian.selector); + _saving.toggleTrusted(alice); + vm.startPrank(governor); + vm.expectEmit(address(_saving)); + emit ToggledTrusted(alice, 1); + _saving.toggleTrusted(alice); + assertEq(_saving.isTrustedUpdater(alice), 1); + + vm.expectEmit(address(_saving)); + emit ToggledTrusted(address(_saving), 1); + _saving.toggleTrusted(address(_saving)); + assertEq(_saving.isTrustedUpdater(address(_saving)), 1); + + vm.expectEmit(address(_saving)); + emit ToggledTrusted(alice, 0); + _saving.toggleTrusted(alice); + assertEq(_saving.isTrustedUpdater(alice), 0); + assertEq(_saving.isTrustedUpdater(address(_saving)), 1); + vm.stopPrank(); + } + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// APRS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ @@ -184,6 +207,46 @@ contract SavingsTest is Fixture, FunctionUtils { ); } + function testFuzz_SetRateWithTrusted(uint256 rate) public { + // we need to decrease to a smaller maxRate = 37% otherwise the approximation is way off + // even currently we can not achieve a 0.1% precision + rate = bound(rate, _minRate, _maxRate / 10); + vm.prank(governor); + vm.expectEmit(address(_saving)); + emit ToggledTrusted(alice, 1); + _saving.toggleTrusted(alice); + vm.stopPrank(); + + vm.prank(alice); + vm.expectEmit(address(_saving)); + emit RateUpdated(rate); + _saving.setRate(uint208(rate)); + + assertEq(_saving.rate(), rate); + uint256 estimatedAPR = (BASE_18 * unwrap(powu(ud(BASE_18 + rate / BASE_9), 365 days))) / + unwrap(powu(ud(BASE_18), 365 days)) - + BASE_18; + + _assertApproxEqRelDecimalWithTolerance( + _saving.estimatedAPR(), + estimatedAPR, + estimatedAPR, + _MAX_PERCENTAGE_DEVIATION * 5000, + 18 + ); + + vm.prank(governor); + vm.expectEmit(address(_saving)); + emit ToggledTrusted(alice, 0); + _saving.toggleTrusted(alice); + vm.stopPrank(); + + vm.prank(alice); + vm.expectRevert(Errors.NotTrusted.selector); + _saving.setRate(uint208(rate)); + vm.stopPrank(); + } + function testFuzz_SetMaxRate(uint256 rate) public { rate = bound(rate, _minRate, _maxRate); vm.prank(governor); @@ -208,17 +271,17 @@ contract SavingsTest is Fixture, FunctionUtils { _saving.setMaxRate(uint208(rate - 1)); } - function test_RevertWhen_SetRateNotGovernorOrGuardian(uint256 rate) public { + function test_RevertWhen_SetRateNotTrusted(uint256 rate) public { rate = bound(rate, _minRate, _maxRate / 10 - 1); vm.prank(governor); _saving.setRate(uint208(rate)); assertEq(_saving.rate(), rate); - vm.expectRevert(Errors.NotGovernorOrGuardian.selector); + vm.expectRevert(Errors.NotTrusted.selector); _saving.setRate(uint208(rate + 1)); - vm.expectRevert(Errors.NotGovernorOrGuardian.selector); + vm.expectRevert(Errors.NotTrusted.selector); _saving.setRate(uint208(rate - 1)); vm.prank(guardian); @@ -226,7 +289,7 @@ contract SavingsTest is Fixture, FunctionUtils { assertEq(_saving.rate(), rate - 1); - vm.expectRevert(Errors.NotGovernorOrGuardian.selector); + vm.expectRevert(Errors.NotTrusted.selector); _saving.setRate(uint208(0)); vm.prank(guardian); diff --git a/test/units/upgrade/SavingsNameable.t.sol b/test/units/upgrade/SavingsNameable.t.sol new file mode 100644 index 00000000..491fc90e --- /dev/null +++ b/test/units/upgrade/SavingsNameable.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "../../utils/Helper.sol"; +import { Test } from "forge-std/Test.sol"; +import "utils/src/Constants.sol"; +import { SavingsNameable } from "contracts/savings/nameable/SavingsNameable.sol"; +import { ProxyAdmin } from "oz/proxy/transparent/ProxyAdmin.sol"; +import { IERC20Metadata } from "oz/interfaces/IERC20Metadata.sol"; +import { TransparentUpgradeableProxy } from "oz/proxy/transparent/TransparentUpgradeableProxy.sol"; + +contract SavingsNameableTest is Test, Helper { + uint256 constant CHAIN = CHAIN_ETHEREUM; + string constant CHAIN_NAME = "mainnet"; + + address public savings; + address public savingsImpl; + + uint208 public rate; + uint40 public lastUpdate; + uint8 public paused; + uint256 public maxRate; + uint256 public totalSupply; + uint256 public totalAssets; + uint256 public previewDeposit; + uint256 public previewMint; + uint256 public previewWithdraw; + uint256 public previewRedeem; + + function setUp() public { + vm.createSelectFork(CHAIN_NAME); + + savings = _chainToContract(CHAIN, ContractType.StUSD); + + assertEq(IERC20Metadata(savings).name(), "Staked USDA"); + assertEq(IERC20Metadata(savings).symbol(), "stUSD"); + rate = SavingsNameable(savings).rate(); + lastUpdate = SavingsNameable(savings).lastUpdate(); + paused = SavingsNameable(savings).paused(); + maxRate = SavingsNameable(savings).maxRate(); + totalSupply = SavingsNameable(savings).totalSupply(); + totalAssets = SavingsNameable(savings).totalAssets(); + previewDeposit = SavingsNameable(savings).previewDeposit(BASE_18); + previewMint = SavingsNameable(savings).previewMint(BASE_18); + previewWithdraw = SavingsNameable(savings).previewWithdraw(BASE_18); + previewRedeem = SavingsNameable(savings).previewRedeem(BASE_18); + + savingsImpl = address(new SavingsNameable()); + } + + function _upgradeContract(string memory name, string memory symbol) internal { + ProxyAdmin proxyAdmin; + if (CHAIN == CHAIN_BASE || CHAIN == CHAIN_POLYGONZKEVM) + proxyAdmin = ProxyAdmin(_chainToContract(CHAIN, ContractType.ProxyAdminGuardian)); + else proxyAdmin = ProxyAdmin(_chainToContract(CHAIN, ContractType.ProxyAdmin)); + + address governor = _chainToContract(CHAIN, ContractType.GovernorMultisig); + address guardian = _chainToContract(CHAIN, ContractType.GuardianMultisig); + + // vm.prank(guardian, guardian); + if (CHAIN == CHAIN_BASE || CHAIN == CHAIN_POLYGONZKEVM) vm.prank(guardian, guardian); + else vm.prank(governor, governor); + proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(savings)), savingsImpl); + + vm.prank(governor, governor); + SavingsNameable(savings).setNameAndSymbol(name, symbol); + } + + function test_NameAndSymbol() public { + _upgradeContract("Staked USDA", "stUSD"); + + assertEq(IERC20Metadata(savings).name(), "Staked USDA"); + assertEq(IERC20Metadata(savings).symbol(), "stUSD"); + } + + function test_Rate() public { + assertEq(SavingsNameable(savings).rate(), rate); + } + + function test_LastUpdate() public { + assertEq(SavingsNameable(savings).lastUpdate(), lastUpdate); + } + + function test_Paused() public { + assertEq(SavingsNameable(savings).paused(), paused); + } + + function test_MaxRate() public { + assertEq(SavingsNameable(savings).maxRate(), maxRate); + } + + function test_TotalSupply() public { + assertEq(SavingsNameable(savings).totalSupply(), totalSupply); + } + + function test_TotalAssets() public { + assertEq(SavingsNameable(savings).totalAssets(), totalAssets); + } + + function test_PreviewDeposit() public { + assertEq(SavingsNameable(savings).previewDeposit(BASE_18), previewDeposit); + } + + function test_PreviewMint() public { + assertEq(SavingsNameable(savings).previewMint(BASE_18), previewMint); + } + + function test_PreviewWithdraw() public { + assertEq(SavingsNameable(savings).previewWithdraw(BASE_18), previewWithdraw); + } + + function test_PreviewRedeem() public { + assertEq(SavingsNameable(savings).previewRedeem(BASE_18), previewRedeem); + } +} From 19cb0b8afe37cbfba030d02f7c7b85a48ef86f1e Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Thu, 23 May 2024 18:20:46 +0200 Subject: [PATCH 06/20] finish savings tests --- contracts/savings/Savings.sol | 2 +- test/fuzz/SavingsNameableTest.t.sol | 127 ++++++++++++++++++ ...t.sol => SavingsNameableUpgradeTest.t.sol} | 80 ++++------- 3 files changed, 155 insertions(+), 54 deletions(-) create mode 100644 test/fuzz/SavingsNameableTest.t.sol rename test/units/upgrade/{SavingsNameable.t.sol => SavingsNameableUpgradeTest.t.sol} (81%) diff --git a/contracts/savings/Savings.sol b/contracts/savings/Savings.sol index c15d19d5..1d4c6209 100644 --- a/contracts/savings/Savings.sol +++ b/contracts/savings/Savings.sol @@ -32,7 +32,7 @@ contract Savings is BaseSavings { /// @notice Checks whether the address is trusted to set the rate mapping(address => uint256) public isTrustedUpdater; - uint256[49] private __gap; + uint256[48] private __gap; /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// EVENTS diff --git a/test/fuzz/SavingsNameableTest.t.sol b/test/fuzz/SavingsNameableTest.t.sol new file mode 100644 index 00000000..ef220e7d --- /dev/null +++ b/test/fuzz/SavingsNameableTest.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import { SafeERC20 } from "oz/token/ERC20/utils/SafeERC20.sol"; +import { IERC20Metadata } from "../mock/MockTokenPermit.sol"; +import "../Fixture.sol"; +import "../utils/FunctionUtils.sol"; +import "contracts/utils/Errors.sol" as Errors; +import "contracts/savings/nameable/SavingsNameable.sol"; +import { UD60x18, ud, pow, powu, unwrap } from "prb/math/UD60x18.sol"; + +import { stdError } from "forge-std/Test.sol"; + +contract SavingsNameableTest is Fixture, FunctionUtils { + using SafeERC20 for IERC20; + + uint256 internal constant _initDeposit = 1e12; + SavingsNameable internal _saving; + Savings internal _savingImplementation; + string internal _name; + string internal _symbol; + + function setUp() public override { + super.setUp(); + + _savingImplementation = new SavingsNameable(); + bytes memory data; + _saving = SavingsNameable(_deployUpgradeable(address(proxyAdmin), address(_savingImplementation), data)); + _name = "Staked EURA"; + _symbol = "stEUR"; + + vm.startPrank(governor); + agToken.addMinter(address(_saving)); + deal(address(agToken), governor, _initDeposit); + agToken.approve(address(_saving), _initDeposit); + _saving.initialize( + accessControlManager, + IERC20MetadataUpgradeable(address(agToken)), + _name, + _symbol, + BASE_18 / _initDeposit + ); + _saving.setMaxRate(type(uint256).max); + vm.stopPrank(); + } + + function test_Initialization() public { + assertEq(address(_saving.accessControlManager()), address(accessControlManager)); + assertEq(_saving.asset(), address(agToken)); + assertEq(_saving.name(), _name); + assertEq(_saving.symbol(), _symbol); + assertEq(_saving.totalAssets(), _initDeposit); + assertEq(_saving.totalSupply(), _initDeposit); + assertEq(agToken.balanceOf(address(_saving)), _initDeposit); + assertEq(_saving.balanceOf(address(governor)), 0); + assertEq(_saving.balanceOf(address(_saving)), _initDeposit); + } + + function test_Initialize() public { + // To have the test written at least once somewhere + assert(accessControlManager.isGovernor(governor)); + assert(accessControlManager.isGovernorOrGuardian(guardian)); + assert(accessControlManager.isGovernorOrGuardian(governor)); + bytes memory data; + Savings savingsContract = Savings( + _deployUpgradeable(address(proxyAdmin), address(_savingImplementation), data) + ); + Savings savingsContract2 = Savings( + _deployUpgradeable(address(proxyAdmin), address(_savingImplementation), data) + ); + + vm.startPrank(governor); + agToken.addMinter(address(savingsContract)); + deal(address(agToken), governor, _initDeposit * 10); + agToken.approve(address(savingsContract), _initDeposit); + agToken.approve(address(savingsContract2), _initDeposit); + + savingsContract.initialize( + accessControlManager, + IERC20MetadataUpgradeable(address(agToken)), + _name, + _symbol, + BASE_18 / _initDeposit + ); + + vm.expectRevert(); + savingsContract.initialize( + accessControlManager, + IERC20MetadataUpgradeable(address(agToken)), + _name, + _symbol, + BASE_18 / _initDeposit + ); + + vm.expectRevert(Errors.ZeroAddress.selector); + savingsContract2.initialize( + IAccessControlManager(address(0)), + IERC20MetadataUpgradeable(address(agToken)), + _name, + _symbol, + BASE_18 / _initDeposit + ); + + vm.stopPrank(); + + assertEq(address(savingsContract.accessControlManager()), address(accessControlManager)); + assertEq(savingsContract.asset(), address(agToken)); + assertEq(savingsContract.name(), _name); + assertEq(savingsContract.symbol(), _symbol); + assertEq(savingsContract.totalAssets(), _initDeposit); + assertEq(savingsContract.totalSupply(), _initDeposit); + assertEq(agToken.balanceOf(address(savingsContract)), _initDeposit); + assertEq(savingsContract.balanceOf(address(governor)), 0); + assertEq(savingsContract.balanceOf(address(savingsContract)), _initDeposit); + } + + function test_SetNameAndSymbol() public { + vm.expectRevert(Errors.NotGovernor.selector); + _saving.setNameAndSymbol("EURA Test", "EURA"); + + vm.startPrank(governor); + _saving.setNameAndSymbol("EURA Test", "EURA"); + assertEq(_saving.name(), "EURA Test"); + assertEq(_saving.symbol(), "EURA"); + vm.stopPrank(); + } +} diff --git a/test/units/upgrade/SavingsNameable.t.sol b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol similarity index 81% rename from test/units/upgrade/SavingsNameable.t.sol rename to test/units/upgrade/SavingsNameableUpgradeTest.t.sol index 491fc90e..b0cbff4f 100644 --- a/test/units/upgrade/SavingsNameable.t.sol +++ b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol @@ -9,7 +9,7 @@ import { ProxyAdmin } from "oz/proxy/transparent/ProxyAdmin.sol"; import { IERC20Metadata } from "oz/interfaces/IERC20Metadata.sol"; import { TransparentUpgradeableProxy } from "oz/proxy/transparent/TransparentUpgradeableProxy.sol"; -contract SavingsNameableTest is Test, Helper { +contract SavingsNameableUpgradeTest is Test, Helper { uint256 constant CHAIN = CHAIN_ETHEREUM; string constant CHAIN_NAME = "mainnet"; @@ -26,12 +26,27 @@ contract SavingsNameableTest is Test, Helper { uint256 public previewMint; uint256 public previewWithdraw; uint256 public previewRedeem; + address public governor; + address public guardian; + ProxyAdmin public proxyAdmin; function setUp() public { vm.createSelectFork(CHAIN_NAME); savings = _chainToContract(CHAIN, ContractType.StUSD); + if (CHAIN == CHAIN_BASE || CHAIN == CHAIN_POLYGONZKEVM) + proxyAdmin = ProxyAdmin(_chainToContract(CHAIN, ContractType.ProxyAdminGuardian)); + else proxyAdmin = ProxyAdmin(_chainToContract(CHAIN, ContractType.ProxyAdmin)); + governor = _chainToContract(CHAIN, ContractType.GovernorMultisig); + guardian = _chainToContract(CHAIN, ContractType.GuardianMultisig); + + // TODO: to be removed when chainToContract works + savings = 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776; + proxyAdmin = ProxyAdmin(0x1D941EF0D3Bba4ad67DBfBCeE5262F4CEE53A32b); + governor = 0xdC4e6DFe07EFCa50a197DF15D9200883eF4Eb1c8; + guardian = 0x0C2553e4B9dFA9f83b1A6D3EAB96c4bAaB42d430; + assertEq(IERC20Metadata(savings).name(), "Staked USDA"); assertEq(IERC20Metadata(savings).symbol(), "stUSD"); rate = SavingsNameable(savings).rate(); @@ -46,70 +61,29 @@ contract SavingsNameableTest is Test, Helper { previewRedeem = SavingsNameable(savings).previewRedeem(BASE_18); savingsImpl = address(new SavingsNameable()); + _upgradeContract("Staked USDA", "stUSD"); } function _upgradeContract(string memory name, string memory symbol) internal { - ProxyAdmin proxyAdmin; - if (CHAIN == CHAIN_BASE || CHAIN == CHAIN_POLYGONZKEVM) - proxyAdmin = ProxyAdmin(_chainToContract(CHAIN, ContractType.ProxyAdminGuardian)); - else proxyAdmin = ProxyAdmin(_chainToContract(CHAIN, ContractType.ProxyAdmin)); - - address governor = _chainToContract(CHAIN, ContractType.GovernorMultisig); - address guardian = _chainToContract(CHAIN, ContractType.GuardianMultisig); - - // vm.prank(guardian, guardian); if (CHAIN == CHAIN_BASE || CHAIN == CHAIN_POLYGONZKEVM) vm.prank(guardian, guardian); else vm.prank(governor, governor); proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(savings)), savingsImpl); - vm.prank(governor, governor); SavingsNameable(savings).setNameAndSymbol(name, symbol); } - function test_NameAndSymbol() public { - _upgradeContract("Staked USDA", "stUSD"); - + function test_UpdatedValues() public { assertEq(IERC20Metadata(savings).name(), "Staked USDA"); assertEq(IERC20Metadata(savings).symbol(), "stUSD"); - } - - function test_Rate() public { - assertEq(SavingsNameable(savings).rate(), rate); - } - - function test_LastUpdate() public { - assertEq(SavingsNameable(savings).lastUpdate(), lastUpdate); - } - - function test_Paused() public { - assertEq(SavingsNameable(savings).paused(), paused); - } - - function test_MaxRate() public { - assertEq(SavingsNameable(savings).maxRate(), maxRate); - } - - function test_TotalSupply() public { - assertEq(SavingsNameable(savings).totalSupply(), totalSupply); - } - - function test_TotalAssets() public { - assertEq(SavingsNameable(savings).totalAssets(), totalAssets); - } - - function test_PreviewDeposit() public { - assertEq(SavingsNameable(savings).previewDeposit(BASE_18), previewDeposit); - } - - function test_PreviewMint() public { - assertEq(SavingsNameable(savings).previewMint(BASE_18), previewMint); - } - - function test_PreviewWithdraw() public { - assertEq(SavingsNameable(savings).previewWithdraw(BASE_18), previewWithdraw); - } - - function test_PreviewRedeem() public { assertEq(SavingsNameable(savings).previewRedeem(BASE_18), previewRedeem); + assertEq(SavingsNameable(savings).previewWithdraw(BASE_18), previewWithdraw); + assertEq(SavingsNameable(savings).previewMint(BASE_18), previewMint); + assertEq(SavingsNameable(savings).previewDeposit(BASE_18), previewDeposit); + assertEq(SavingsNameable(savings).totalAssets(), totalAssets); + assertEq(SavingsNameable(savings).totalSupply(), totalSupply); + assertEq(SavingsNameable(savings).maxRate(), maxRate); + assertEq(SavingsNameable(savings).paused(), paused); + assertEq(SavingsNameable(savings).lastUpdate(), lastUpdate); + assertEq(SavingsNameable(savings).rate(), rate); } } From 42b26de3d4e3ba7dfeab2433132bb4316e42442b Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Fri, 24 May 2024 14:39:49 +0200 Subject: [PATCH 07/20] finishing harvesting tests --- contracts/helpers/Harvester.sol | 65 +++---- test/fuzz/Harvester.t.sol | 244 +++++++++++++++++++++++++++ test/scripts/HarvesterUSDATest.t.sol | 226 +++++++++++++++++++++++++ 3 files changed, 506 insertions(+), 29 deletions(-) create mode 100644 test/fuzz/Harvester.t.sol create mode 100644 test/scripts/HarvesterUSDATest.t.sol diff --git a/contracts/helpers/Harvester.sol b/contracts/helpers/Harvester.sol index a47a5b42..df5b37ed 100644 --- a/contracts/helpers/Harvester.sol +++ b/contracts/helpers/Harvester.sol @@ -15,6 +15,19 @@ import "../utils/Errors.sol"; import { RebalancerFlashloan } from "./RebalancerFlashloan.sol"; +struct CollatParams { + // Vault associated to the collateral + address vault; + // Target exposure to the collateral asset used in the vault + uint64 targetExposure; + // Maximum exposure within the Transmuter to the vault asset + uint64 maxExposureYieldAsset; + // Minimum exposure within the Transmuter to the vault asset + uint64 minExposureYieldAsset; + // Whether limit exposures should be overriden or read onchain through the Transmuter + uint64 overrideExposures; +} + /// @title Harvester /// @author Angle Labs, Inc. /// @dev Contract for anyone to permissionlessly adjust the reserves of Angle Transmuter through the RebalancerFlashloan contract @@ -22,19 +35,6 @@ contract Harvester is AccessControl { using SafeERC20 for IERC20; using SafeCast for uint256; - struct CollateralSetup { - // Vault associated to the collateral - address vault; - // Target exposure to the collateral asset used in the vault - uint64 targetExposure; - // Maximum exposure within the Transmuter to the vault asset - uint64 maxExposureYieldAsset; - // Minimum exposure within the Transmuter to the vault asset - uint64 minExposureYieldAsset; - // Whether limit exposures should be overriden or read onchain through the Transmuter - uint64 overrideExposures; - } - /// @notice Reference to the `transmuter` implementation this contract aims at rebalancing ITransmuter public immutable TRANSMUTER; /// @notice Permissioned rebalancer contract @@ -42,25 +42,27 @@ contract Harvester is AccessControl { /// @notice Max slippage when dealing with the Transmuter uint96 public maxSlippage; /// @notice Data associated to a collateral - mapping(address => CollateralSetup) public collateralData; + mapping(address => CollatParams) public collateralData; /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// INITIALIZATION //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ constructor( - RebalancerFlashloan _rebalancer, + address _rebalancer, address vault, uint64 targetExposure, uint64 overrideExposures, uint64 maxExposureYieldAsset, - uint64 minExposureYieldAsset + uint64 minExposureYieldAsset, + uint96 _maxSlippage ) { - ITransmuter transmuter = _rebalancer.TRANSMUTER(); + ITransmuter transmuter = RebalancerFlashloan(_rebalancer).TRANSMUTER(); TRANSMUTER = transmuter; - rebalancer = _rebalancer; + rebalancer = RebalancerFlashloan(_rebalancer); accessControlManager = IAccessControlManager(transmuter.accessControlManager()); _setCollateralData(vault, targetExposure, minExposureYieldAsset, maxExposureYieldAsset, overrideExposures); + _setMaxSlippage(_maxSlippage); } /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -74,15 +76,15 @@ contract Harvester is AccessControl { /// to the target exposure function harvest(address collateral) external { (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = TRANSMUTER.getIssuedByCollateral(collateral); - CollateralSetup memory collatInfo = collateralData[collateral]; + CollatParams memory collatInfo = collateralData[collateral]; (uint256 stablecoinsFromVault, ) = TRANSMUTER.getIssuedByCollateral(collatInfo.vault); uint8 increase; uint256 amount; - uint256 targetExposureLiquid = collatInfo.targetExposure; - if (stablecoinsFromCollateral * 1e9 > targetExposureLiquid * stablecoinsIssued) { + uint256 targetExposureScaled = collatInfo.targetExposure * stablecoinsIssued; + if (stablecoinsFromCollateral * 1e9 > targetExposureScaled) { // Need to increase exposure to yield bearing asset increase = 1; - amount = stablecoinsFromCollateral - (targetExposureLiquid * stablecoinsIssued) / 1e9; + amount = stablecoinsFromCollateral - targetExposureScaled / 1e9; uint256 maxValueScaled = collatInfo.maxExposureYieldAsset * stablecoinsIssued; // These checks assume that there are no transaction fees on the stablecoin->collateral conversion and so // it's still possible that exposure goes above the max exposure in some rare cases @@ -92,14 +94,15 @@ contract Harvester is AccessControl { } else { // In this case, exposure after the operation might remain slightly below the targetExposure as less // collateral may be obtained by burning stablecoins for the yield asset and unwrapping it - amount = (targetExposureLiquid * stablecoinsIssued) / 1e9 - stablecoinsFromCollateral; + amount = targetExposureScaled / 1e9 - stablecoinsFromCollateral; uint256 minValueScaled = collatInfo.minExposureYieldAsset * stablecoinsIssued; if (stablecoinsFromVault * 1e9 < minValueScaled) amount = 0; else if (stablecoinsFromVault * 1e9 < minValueScaled + amount * 1e9) amount = stablecoinsFromVault - minValueScaled / 1e9; } if (amount > 0) { - TRANSMUTER.updateOracle(collatInfo.vault); + try TRANSMUTER.updateOracle(collatInfo.vault) {} catch {} + rebalancer.adjustYieldExposure( amount, increase, @@ -130,15 +133,19 @@ contract Harvester is AccessControl { } function setMaxSlippage(uint96 _maxSlippage) external onlyGuardian { - if (maxSlippage > 1e9) revert InvalidParam(); - maxSlippage = _maxSlippage; + _setMaxSlippage(_maxSlippage); } function updateLimitExposuresYieldAsset(address collateral) external { - CollateralSetup storage collatInfo = collateralData[collateral]; + CollatParams storage collatInfo = collateralData[collateral]; if (collatInfo.overrideExposures == 0) _updateLimitExposuresYieldAsset(collatInfo); } + function _setMaxSlippage(uint96 _maxSlippage) internal { + if (_maxSlippage > 1e9) revert InvalidParam(); + maxSlippage = _maxSlippage; + } + function _setCollateralData( address vault, uint64 targetExposure, @@ -147,7 +154,7 @@ contract Harvester is AccessControl { uint64 overrideExposures ) internal { address collateral = address(IERC4626(vault).asset()); - CollateralSetup storage collatInfo = collateralData[collateral]; + CollatParams storage collatInfo = collateralData[collateral]; collatInfo.vault = vault; if (targetExposure >= 1e9) revert InvalidParam(); collatInfo.targetExposure = targetExposure; @@ -161,7 +168,7 @@ contract Harvester is AccessControl { } } - function _updateLimitExposuresYieldAsset(CollateralSetup storage collatInfo) internal { + function _updateLimitExposuresYieldAsset(CollatParams storage collatInfo) internal { uint64[] memory xFeeMint; (xFeeMint, ) = TRANSMUTER.getCollateralMintFees(collatInfo.vault); uint256 length = xFeeMint.length; diff --git a/test/fuzz/Harvester.t.sol b/test/fuzz/Harvester.t.sol new file mode 100644 index 00000000..6234d0ff --- /dev/null +++ b/test/fuzz/Harvester.t.sol @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import { SafeERC20 } from "oz/token/ERC20/utils/SafeERC20.sol"; + +import { stdError } from "forge-std/Test.sol"; + +import "contracts/utils/Errors.sol" as Errors; + +import "../Fixture.sol"; +import { IERC20Metadata } from "../mock/MockTokenPermit.sol"; +import "../utils/FunctionUtils.sol"; + +import "contracts/savings/Savings.sol"; +import "../mock/MockTokenPermit.sol"; +import "contracts/helpers/RebalancerFlashloan.sol"; +import "contracts/helpers/Harvester.sol"; + +contract HarvesterTest is Fixture, FunctionUtils { + using SafeERC20 for IERC20; + + RebalancerFlashloan public rebalancer; + Harvester public harvester; + Savings internal _saving; + address internal _savingImplementation; + string internal _name; + string internal _symbol; + address public collat; + uint64 public targetExposure; + uint64 public maxExposureYieldAsset; + uint64 public minExposureYieldAsset; + + function setUp() public override { + super.setUp(); + + MockTokenPermit token = new MockTokenPermit("EURC", "EURC", 6); + collat = address(token); + + _savingImplementation = address(new Savings()); + bytes memory data; + _saving = Savings(_deployUpgradeable(address(proxyAdmin), _savingImplementation, data)); + _name = "savingAgEUR"; + _symbol = "SAGEUR"; + + vm.startPrank(governor); + token.mint(governor, 1e12); + token.approve(address(_saving), 1e12); + _saving.initialize(accessControlManager, IERC20MetadataUpgradeable(address(token)), _name, _symbol, BASE_6); + vm.stopPrank(); + targetExposure = uint64((15 * 1e9) / 100); + maxExposureYieldAsset = uint64((80 * 1e9) / 100); + minExposureYieldAsset = uint64((5 * 1e9) / 100); + rebalancer = new RebalancerFlashloan(accessControlManager, transmuter, IERC3156FlashLender(governor)); + harvester = new Harvester( + address(rebalancer), + address(_saving), + targetExposure, + 1, + maxExposureYieldAsset, + minExposureYieldAsset, + 1e8 + ); + } + + function test_RebalancerInitialization() public { + assertEq(address(harvester.rebalancer()), address(rebalancer)); + assertEq(address(harvester.TRANSMUTER()), address(transmuter)); + assertEq(address(harvester.accessControlManager()), address(accessControlManager)); + (address vault, uint64 target, uint64 maxi, uint64 mini, uint64 overrideExp) = harvester.collateralData(collat); + assertEq(vault, address(_saving)); + assertEq(target, targetExposure); + assertEq(maxi, maxExposureYieldAsset); + assertEq(mini, minExposureYieldAsset); + assertEq(overrideExp, 1); + } + + function test_Constructor_RevertWhen_InvalidParams() public { + vm.expectRevert(); + new Harvester( + address(0), + address(_saving), + targetExposure, + 1, + maxExposureYieldAsset, + minExposureYieldAsset, + 1e8 + ); + + vm.expectRevert(); + new Harvester( + address(rebalancer), + address(0), + targetExposure, + 1, + maxExposureYieldAsset, + minExposureYieldAsset, + 1e8 + ); + + vm.expectRevert(Errors.InvalidParam.selector); + harvester = new Harvester( + address(rebalancer), + address(_saving), + 1e10, + 1, + maxExposureYieldAsset, + minExposureYieldAsset, + 1e8 + ); + + vm.expectRevert(Errors.InvalidParam.selector); + harvester = new Harvester( + address(rebalancer), + address(_saving), + 1e10, + 0, + maxExposureYieldAsset, + minExposureYieldAsset, + 1e8 + ); + + vm.expectRevert(Errors.InvalidParam.selector); + harvester = new Harvester(address(rebalancer), address(_saving), 1e9 / 10, 1, 1e10, minExposureYieldAsset, 1e8); + + vm.expectRevert(Errors.InvalidParam.selector); + harvester = new Harvester(address(rebalancer), address(_saving), 1e9 / 10, 1, 1e8, 2e8, 1e8); + + vm.expectRevert(Errors.InvalidParam.selector); + harvester = new Harvester( + address(rebalancer), + address(_saving), + targetExposure, + 1, + maxExposureYieldAsset, + minExposureYieldAsset, + 1e10 + ); + } + + function test_OnlyGuardian_RevertWhen_NotGuardian() public { + vm.expectRevert(Errors.NotGovernorOrGuardian.selector); + harvester.setRebalancer(alice); + + vm.expectRevert(Errors.NotGovernorOrGuardian.selector); + harvester.setCollateralData(address(_saving), targetExposure, 1, maxExposureYieldAsset, minExposureYieldAsset); + + vm.expectRevert(Errors.NotGovernorOrGuardian.selector); + harvester.setMaxSlippage(1e9); + + harvester.updateLimitExposuresYieldAsset(collat); + } + + function test_SettersHarvester() public { + vm.startPrank(governor); + vm.expectRevert(Errors.InvalidParam.selector); + harvester.setMaxSlippage(1e10); + + harvester.setMaxSlippage(123456); + assertEq(harvester.maxSlippage(), 123456); + + vm.expectRevert(Errors.ZeroAddress.selector); + harvester.setRebalancer(address(0)); + + harvester.setRebalancer(address(harvester)); + assertEq(address(harvester.rebalancer()), address(harvester)); + + harvester.setCollateralData( + address(_saving), + targetExposure + 10, + minExposureYieldAsset - 1, + maxExposureYieldAsset + 1, + 1 + ); + (address vault, uint64 target, uint64 maxi, uint64 mini, uint64 overrideExp) = harvester.collateralData(collat); + assertEq(vault, address(_saving)); + assertEq(target, targetExposure + 10); + assertEq(maxi, maxExposureYieldAsset + 1); + assertEq(mini, minExposureYieldAsset - 1); + assertEq(overrideExp, 1); + + harvester.setCollateralData( + address(_saving), + targetExposure + 10, + minExposureYieldAsset - 1, + maxExposureYieldAsset + 1, + 0 + ); + (vault, target, maxi, mini, overrideExp) = harvester.collateralData(collat); + assertEq(vault, address(_saving)); + assertEq(target, targetExposure + 10); + assertEq(maxi, 1e9); + assertEq(mini, 0); + assertEq(overrideExp, 0); + + vm.stopPrank(); + } + + function test_UpdateLimitExposuresYieldAsset() public { + bytes memory data; + Savings newVault = Savings(_deployUpgradeable(address(proxyAdmin), _savingImplementation, data)); + _name = "savingAgEUR"; + _symbol = "SAGEUR"; + + vm.startPrank(governor); + MockTokenPermit(address(eurA)).mint(governor, 1e12); + eurA.approve(address(newVault), 1e12); + newVault.initialize(accessControlManager, IERC20MetadataUpgradeable(address(eurA)), _name, _symbol, BASE_6); + transmuter.addCollateral(address(newVault)); + vm.stopPrank(); + + uint64[] memory xFeeMint = new uint64[](3); + int64[] memory yFeeMint = new int64[](3); + + xFeeMint[0] = 0; + xFeeMint[1] = uint64((15 * BASE_9) / 100); + xFeeMint[2] = uint64((2 * BASE_9) / 10); + + yFeeMint[0] = int64(1); + yFeeMint[1] = int64(uint64(BASE_9 / 10)); + yFeeMint[2] = int64(uint64((2 * BASE_9) / 10)); + + uint64[] memory xFeeBurn = new uint64[](3); + int64[] memory yFeeBurn = new int64[](3); + + xFeeBurn[0] = uint64(BASE_9); + xFeeBurn[1] = uint64(BASE_9 / 10); + xFeeBurn[2] = 0; + + yFeeBurn[0] = int64(1); + yFeeBurn[1] = int64(1); + yFeeBurn[2] = int64(uint64(BASE_9 / 10)); + + vm.startPrank(governor); + transmuter.setFees(address(newVault), xFeeBurn, yFeeBurn, false); + transmuter.setFees(address(newVault), xFeeMint, yFeeMint, true); + harvester.setCollateralData(address(newVault), targetExposure, minExposureYieldAsset, maxExposureYieldAsset, 0); + harvester.updateLimitExposuresYieldAsset(address(eurA)); + + (, , uint64 maxi, uint64 mini, ) = harvester.collateralData(address(eurA)); + assertEq(maxi, (15 * BASE_9) / 100); + assertEq(mini, BASE_9 / 10); + vm.stopPrank(); + } +} diff --git a/test/scripts/HarvesterUSDATest.t.sol b/test/scripts/HarvesterUSDATest.t.sol new file mode 100644 index 00000000..ac7ddff7 --- /dev/null +++ b/test/scripts/HarvesterUSDATest.t.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import { stdJson } from "forge-std/StdJson.sol"; +import { console } from "forge-std/console.sol"; +import { Test } from "forge-std/Test.sol"; + +import "../../scripts/Constants.s.sol"; + +import "contracts/utils/Errors.sol" as Errors; +import "contracts/transmuter/Storage.sol" as Storage; +import "contracts/transmuter/libraries/LibHelpers.sol"; +import { CollateralSetupProd } from "contracts/transmuter/configs/ProductionTypes.sol"; +import { ITransmuter } from "interfaces/ITransmuter.sol"; +import "utils/src/Constants.sol"; +import { IERC20 } from "oz/interfaces/IERC20.sol"; +import { IAgToken } from "interfaces/IAgToken.sol"; +import { Harvester } from "contracts/helpers/Harvester.sol"; + +import { RebalancerFlashloan, IERC4626, IERC3156FlashLender } from "contracts/helpers/RebalancerFlashloan.sol"; + +interface IFlashAngle { + function addStablecoinSupport(address _treasury) external; + + function setFlashLoanParameters(address stablecoin, uint64 _flashLoanFee, uint256 _maxBorrowable) external; +} + +contract HarvesterUSDATest is Test { + using stdJson for string; + + ITransmuter transmuter; + IERC20 USDA; + IAgToken treasuryUSDA; + IFlashAngle FLASHLOAN; + address governor; + RebalancerFlashloan public rebalancer; + uint256 ethereumFork; + Harvester harvester; + uint64 public targetExposure; + uint64 public maxExposureYieldAsset; + uint64 public minExposureYieldAsset; + + function setUp() public { + ethereumFork = vm.createSelectFork(vm.envString("ETH_NODE_URI_MAINNET"), 19939091); + + transmuter = ITransmuter(0x222222fD79264BBE280b4986F6FEfBC3524d0137); + USDA = IERC20(0x0000206329b97DB379d5E1Bf586BbDB969C63274); + FLASHLOAN = IFlashAngle(0x4A2FF9bC686A0A23DA13B6194C69939189506F7F); + treasuryUSDA = IAgToken(0xf8588520E760BB0b3bDD62Ecb25186A28b0830ee); + governor = 0xdC4e6DFe07EFCa50a197DF15D9200883eF4Eb1c8; + rebalancer = RebalancerFlashloan(0x22604C0E5633A9810E01c9cb469B23Eee17AC411); + targetExposure = uint64((15 * 1e9) / 100); + maxExposureYieldAsset = uint64((80 * 1e9) / 100); + minExposureYieldAsset = uint64((5 * 1e9) / 100); + + harvester = new Harvester( + address(rebalancer), + address(STEAK_USDC), + targetExposure, + 1, + maxExposureYieldAsset, + minExposureYieldAsset, + 1e8 + ); + + vm.startPrank(governor); + transmuter.toggleWhitelist(Storage.WhitelistType.BACKED, NEW_DEPLOYER); + transmuter.toggleTrusted(governor, Storage.TrustedType.Seller); + transmuter.toggleTrusted(address(harvester), Storage.TrustedType.Seller); + + vm.stopPrank(); + } + + function testUnit_Harvest_IncreaseUSDCExposure() external { + // At current block: USDC exposure = 7.63%, steakUSDC = 75.26%, bIB01 = 17.11% + (uint256 fromUSDC, uint256 total) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + harvester.harvest(USDC); + (uint256 fromUSDC2, uint256 total2) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK2, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC2, fromUSDC); + assertGt(fromSTEAK, fromSTEAK2); + assertGt(total, total2); + assertApproxEqRel((fromUSDC2 * 1e9) / total2, targetExposure, 100 * BPS); + + harvester.harvest(USDC); + (uint256 fromUSDC3, uint256 total3) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK3, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC3, fromUSDC2); + assertGt(fromSTEAK2, fromSTEAK3); + assertGt(total2, total3); + assertGt((fromUSDC3 * 1e9) / total3, (fromUSDC2 * 1e9) / total2); + assertGt(targetExposure, (fromUSDC3 * 1e9) / total3); + } + + function testUnit_Harvest_IncreaseUSDCExposureButMinValueYield() external { + // At current block: USDC exposure = 7.63%, steakUSDC = 75.26%, bIB01 = 17.11% -> putting below + // min exposure + vm.startPrank(governor); + harvester.setCollateralData(STEAK_USDC, targetExposure, (80 * 1e9) / 100, (90 * 1e9) / 100, 1); + vm.stopPrank(); + (uint256 fromUSDC, uint256 total) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + harvester.harvest(USDC); + (uint256 fromUSDC2, uint256 total2) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK2, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertEq(fromUSDC2, fromUSDC); + assertEq(fromSTEAK, fromSTEAK2); + assertEq(total, total2); + + harvester.harvest(USDC); + (uint256 fromUSDC3, uint256 total3) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK3, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertEq(fromUSDC3, fromUSDC2); + assertEq(fromSTEAK2, fromSTEAK3); + assertEq(total2, total3); + } + + function testUnit_Harvest_IncreaseUSDCExposureButMinValueThresholdReached() external { + // At current block: USDC exposure = 7.63%, steakUSDC = 75.26%, bIB01 = 17.11% -> putting in between + // min exposure and target exposure + vm.startPrank(governor); + harvester.setCollateralData(STEAK_USDC, targetExposure, (73 * 1e9) / 100, (90 * 1e9) / 100, 1); + vm.stopPrank(); + + (uint256 fromUSDC, uint256 total) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + harvester.harvest(USDC); + (uint256 fromUSDC2, uint256 total2) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK2, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC2, fromUSDC); + assertGt(fromSTEAK, fromSTEAK2); + assertGt(total, total2); + assertApproxEqRel((fromSTEAK2 * 1e9) / total2, (73 * 1e9) / 100, 100 * BPS); + + harvester.harvest(USDC); + (uint256 fromUSDC3, uint256 total3) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK3, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC3, fromUSDC2); + assertGt(fromSTEAK2, fromSTEAK3); + assertGt(total2, total3); + assertApproxEqRel((fromSTEAK3 * 1e9) / total3, (fromSTEAK2 * 1e9) / total2, 10 * BPS); + } + + function testUnit_Harvest_DecreaseUSDCExposureClassical() external { + // At current block: USDC exposure = 7.63%, steakUSDC = 75.26%, bIB01 = 17.11% -> putting below target + vm.startPrank(governor); + harvester.setCollateralData(STEAK_USDC, (5 * 1e9) / 100, (73 * 1e9) / 100, (90 * 1e9) / 100, 1); + vm.stopPrank(); + + (uint256 fromUSDC, uint256 total) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + + harvester.harvest(USDC); + + (uint256 fromUSDC2, uint256 total2) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK2, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC, fromUSDC2); + assertGt(fromSTEAK2, fromSTEAK); + assertGt(total, total2); + assertApproxEqRel((fromUSDC2 * 1e9) / total2, (5 * 1e9) / 100, 100 * BPS); + + harvester.harvest(USDC); + (uint256 fromUSDC3, uint256 total3) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK3, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC2, fromUSDC3); + assertGt(fromSTEAK3, fromSTEAK2); + assertGt(total2, total3); + assertGe((fromUSDC2 * 1e9) / total2, (fromUSDC3 * 1e9) / total3); + assertGe((fromUSDC3 * 1e9) / total3, (5 * 1e9) / 100); + } + + function testUnit_Harvest_DecreaseUSDCExposureAlreadyMaxThreshold() external { + // At current block: USDC exposure = 7.63%, steakUSDC = 75.26%, bIB01 = 17.11% -> putting below target + vm.startPrank(governor); + harvester.setCollateralData(STEAK_USDC, (5 * 1e9) / 100, (73 * 1e9) / 100, (74 * 1e9) / 100, 1); + vm.stopPrank(); + + (uint256 fromUSDC, uint256 total) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + + harvester.harvest(USDC); + + (uint256 fromUSDC2, uint256 total2) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK2, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertEq(fromUSDC, fromUSDC2); + assertEq(fromSTEAK2, fromSTEAK); + assertEq(total, total2); + + harvester.harvest(USDC); + (uint256 fromUSDC3, uint256 total3) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK3, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertEq(fromUSDC2, fromUSDC3); + assertEq(fromSTEAK3, fromSTEAK2); + assertEq(total2, total3); + } + + function testUnit_Harvest_DecreaseUSDCExposureTillMaxThreshold() external { + // At current block: USDC exposure = 7.63%, steakUSDC = 75.26%, bIB01 = 17.11% -> putting below target + vm.startPrank(governor); + harvester.setCollateralData(STEAK_USDC, (5 * 1e9) / 100, (73 * 1e9) / 100, (755 * 1e9) / 1000, 1); + vm.stopPrank(); + + (uint256 fromUSDC, uint256 total) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + + harvester.harvest(USDC); + + (uint256 fromUSDC2, uint256 total2) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK2, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC, fromUSDC2); + assertGt(fromSTEAK2, fromSTEAK); + assertGt(total, total2); + assertLe((fromSTEAK2 * 1e9) / total2, (755 * 1e9) / 1000); + assertApproxEqRel((fromSTEAK2 * 1e9) / total2, (755 * 1e9) / 1000, 100 * BPS); + + harvester.harvest(USDC); + + (uint256 fromUSDC3, uint256 total3) = transmuter.getIssuedByCollateral(address(USDC)); + (uint256 fromSTEAK3, ) = transmuter.getIssuedByCollateral(address(STEAK_USDC)); + assertGt(fromUSDC2, fromUSDC3); + assertGt(fromSTEAK3, fromSTEAK2); + assertGt(total2, total3); + assertApproxEqRel((fromSTEAK3 * 1e9) / total3, (fromSTEAK2 * 1e9) / total2, 10 * BPS); + } +} From 703a7caf1aaff8620f4800e2a6429e3265c64380 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Fri, 24 May 2024 14:41:04 +0200 Subject: [PATCH 08/20] fix: lint --- contracts/helpers/Harvester.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/helpers/Harvester.sol b/contracts/helpers/Harvester.sol index df5b37ed..4d65de4f 100644 --- a/contracts/helpers/Harvester.sol +++ b/contracts/helpers/Harvester.sol @@ -30,7 +30,8 @@ struct CollatParams { /// @title Harvester /// @author Angle Labs, Inc. -/// @dev Contract for anyone to permissionlessly adjust the reserves of Angle Transmuter through the RebalancerFlashloan contract +/// @dev Contract for anyone to permissionlessly adjust the reserves of Angle Transmuter through +/// the RebalancerFlashloan contract contract Harvester is AccessControl { using SafeERC20 for IERC20; using SafeCast for uint256; From ad1039025c2d609a41cf7966088d45dd61642772 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 13:05:00 +0200 Subject: [PATCH 09/20] feat: changes --- .github/workflows/ci.yml | 1 + contracts/helpers/Harvester.sol | 6 ++++-- foundry.toml | 2 +- lib/borrow-contracts | 2 +- lib/forge-std | 1 - lib/prb-math | 1 - test/fuzz/Harvester.t.sol | 6 +++--- 7 files changed, 10 insertions(+), 9 deletions(-) delete mode 160000 lib/forge-std delete mode 160000 lib/prb-math diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eaf1f448..8d4b80a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,7 @@ jobs: # - name: Run Foundry tests # run: yarn test:invariant + # TODO: when uncommenting: add env back test-fuzz: needs: ["build", "lint"] diff --git a/contracts/helpers/Harvester.sol b/contracts/helpers/Harvester.sol index 4d65de4f..0f62da42 100644 --- a/contracts/helpers/Harvester.sol +++ b/contracts/helpers/Harvester.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.23; import { IERC20 } from "oz/interfaces/IERC20.sol"; import { IERC4626 } from "interfaces/external/IERC4626.sol"; @@ -25,6 +25,7 @@ struct CollatParams { // Minimum exposure within the Transmuter to the vault asset uint64 minExposureYieldAsset; // Whether limit exposures should be overriden or read onchain through the Transmuter + // This value should be 1 to override exposures or 2 if these shouldn't be overriden uint64 overrideExposures; } @@ -139,7 +140,7 @@ contract Harvester is AccessControl { function updateLimitExposuresYieldAsset(address collateral) external { CollatParams storage collatInfo = collateralData[collateral]; - if (collatInfo.overrideExposures == 0) _updateLimitExposuresYieldAsset(collatInfo); + if (collatInfo.overrideExposures == 2) _updateLimitExposuresYieldAsset(collatInfo); } function _setMaxSlippage(uint96 _maxSlippage) internal { @@ -165,6 +166,7 @@ contract Harvester is AccessControl { collatInfo.maxExposureYieldAsset = maxExposureYieldAsset; collatInfo.minExposureYieldAsset = minExposureYieldAsset; } else { + collatInfo.overrideExposures = 2; _updateLimitExposuresYieldAsset(collatInfo); } } diff --git a/foundry.toml b/foundry.toml index d4075eaa..18a7be4d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,7 +10,7 @@ via_ir = true sizes = true optimizer = true optimizer_runs=1000 -solc_version = '0.8.22' +solc_version = '0.8.23' ffi = true fs_permissions = [ { access = "read-write", path = "./scripts/selectors.json"}, diff --git a/lib/borrow-contracts b/lib/borrow-contracts index 2d98820b..6c077d7d 160000 --- a/lib/borrow-contracts +++ b/lib/borrow-contracts @@ -1 +1 @@ -Subproject commit 2d98820b712d52e27c8bd4666ad8b17a34a12f95 +Subproject commit 6c077d7d1205308cb801df16d820bdfc4b98a768 diff --git a/lib/forge-std b/lib/forge-std deleted file mode 160000 index 978ac6fa..00000000 --- a/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/lib/prb-math b/lib/prb-math deleted file mode 160000 index 9dc06519..00000000 --- a/lib/prb-math +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9dc06519f3b9f1659fec7d396da634fe690f660c diff --git a/test/fuzz/Harvester.t.sol b/test/fuzz/Harvester.t.sol index 6234d0ff..13244b5c 100644 --- a/test/fuzz/Harvester.t.sol +++ b/test/fuzz/Harvester.t.sol @@ -169,14 +169,14 @@ contract HarvesterTest is Fixture, FunctionUtils { targetExposure + 10, minExposureYieldAsset - 1, maxExposureYieldAsset + 1, - 1 + 0 ); (address vault, uint64 target, uint64 maxi, uint64 mini, uint64 overrideExp) = harvester.collateralData(collat); assertEq(vault, address(_saving)); assertEq(target, targetExposure + 10); assertEq(maxi, maxExposureYieldAsset + 1); assertEq(mini, minExposureYieldAsset - 1); - assertEq(overrideExp, 1); + assertEq(overrideExp, 2); harvester.setCollateralData( address(_saving), @@ -190,7 +190,7 @@ contract HarvesterTest is Fixture, FunctionUtils { assertEq(target, targetExposure + 10); assertEq(maxi, 1e9); assertEq(mini, 0); - assertEq(overrideExp, 0); + assertEq(overrideExp, 2); vm.stopPrank(); } From 81e00b2445d87fbc7281d8ffdb5e9e5dd827495e Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 15:05:45 +0200 Subject: [PATCH 10/20] remove libs --- lib/borrow-contracts | 1 - 1 file changed, 1 deletion(-) delete mode 160000 lib/borrow-contracts diff --git a/lib/borrow-contracts b/lib/borrow-contracts deleted file mode 160000 index 6c077d7d..00000000 --- a/lib/borrow-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6c077d7d1205308cb801df16d820bdfc4b98a768 From 65de6b042dfe7418ec350a6e3bbbad12626fa089 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 15:05:48 +0200 Subject: [PATCH 11/20] forge install: openzeppelin-contracts v4.7.3 --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 6809070f..073ebc9b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,7 +20,7 @@ version = v4.7.3 [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts + url = https://github.com//OpenZeppelin/openzeppelin-contracts version = v4.7.3 [submodule "lib/utils"] path = lib/utils From 11ab8485e3a3d127eb3487973d487be4df54d9ba Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 15:05:56 +0200 Subject: [PATCH 12/20] forge install: openzeppelin-contracts-upgradeable v4.7.3 --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 073ebc9b..0250656e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,7 +16,7 @@ tags = v2.3 [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable + url = https://github.com//OpenZeppelin/openzeppelin-contracts-upgradeable version = v4.7.3 [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts From e84ac119c54584b45e45d897c461961a49320109 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 15:06:32 +0200 Subject: [PATCH 13/20] forge install: forge-std v1.8.2 --- lib/forge-std | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/forge-std diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 00000000..978ac6fa --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 From acb8817261bec52c15fa913834a49f33fa85f972 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 15:07:01 +0200 Subject: [PATCH 14/20] forge install: prb-math v4.0.2 --- lib/prb-math | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/prb-math diff --git a/lib/prb-math b/lib/prb-math new file mode 160000 index 00000000..9dc06519 --- /dev/null +++ b/lib/prb-math @@ -0,0 +1 @@ +Subproject commit 9dc06519f3b9f1659fec7d396da634fe690f660c From 65bc702b588985bc7f4410dc764cdda2ca24f6bf Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 18:49:32 +0200 Subject: [PATCH 15/20] fix: harvester comments --- .gitmodules | 4 -- README.md | 5 ++- remappings.txt | 3 +- scripts/DeploySavings.s.sol | 9 +++-- scripts/DeploySavingsImplem.s.sol | 45 +++++++++++++++++++++ scripts/DeployTransmuterWithoutFacets.s.sol | 1 - scripts/gnosis/DeploySavingsGnosis.s.sol | 2 +- scripts/gnosis/DeployTransmuterGnosis.s.sol | 4 +- test/fuzz/Harvester.t.sol | 4 +- 9 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 scripts/DeploySavingsImplem.s.sol diff --git a/.gitmodules b/.gitmodules index 0250656e..50213425 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,10 +10,6 @@ path = lib/prb-math url = https://github.com/PaulRBerg/prb-math ignore = dirty -[submodule "lib/borrow-contracts"] - path = lib/borrow-contracts - url = https://github.com/AngleProtocol/borrow-contracts - tags = v2.3 [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com//OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/README.md b/README.md index 249a2bb5..17273f48 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Transmuter is an autonomous and modular price stability module for decentralized - It is conceived as a basket of different assets (normally stablecoins) backing a stablecoin and comes with guarantees on the maximum exposure the stablecoin can have to each asset in the basket. - A stablecoin issued through the Transmuter system can be minted at oracle value from any of the assets with adaptive fees, and it can be burnt for any of the assets in the backing with variable fees as well. It can also be redeemed at any time against a proportional amount of each asset in the backing. -Transmuter is compatible with other common mechanisms often used to issue stablecoins like collateralized-debt position models. It should notably be used as a standalone module within the Angle Protocol for agEUR in parallel with the Borrowing module. +Transmuter is compatible with other common mechanisms often used to issue stablecoins like collateralized-debt position models. It is notably used as a standalone module within the Angle Protocol for EURA in parallel with the Borrowing module. --- @@ -69,7 +69,8 @@ For contracts deployed for the Angle Protocol, a bug bounty is open on [Immunefi ## Deployment Addresses 🚦 -- Transmuter for agEUR on Ethereum: [0x00253582b2a3FE112feEC532221d9708c64cEFAb](https://etherscan.io/address/0x00253582b2a3FE112feEC532221d9708c64cEFAb) +- Transmuter for EURA on Ethereum: [0x00253582b2a3FE112feEC532221d9708c64cEFAb](https://etherscan.io/address/0x00253582b2a3FE112feEC532221d9708c64cEFAb) +- Transmuter for USDA on Ethereum: [0x222222fD79264BBE280b4986F6FEfBC3524d0137](https://etherscan.io/address/0x222222fD79264BBE280b4986F6FEfBC3524d0137) --- diff --git a/remappings.txt b/remappings.txt index 0b856211..1869ac5a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -10,5 +10,4 @@ oz-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ mock/=test/mock/ prb/math/=lib/prb-math/src/ -borrow/=lib/borrow-contracts/contracts -utils/=lib/utils \ No newline at end of file +utils/=lib/utils diff --git a/scripts/DeploySavings.s.sol b/scripts/DeploySavings.s.sol index f2f361f4..3e9d93cf 100644 --- a/scripts/DeploySavings.s.sol +++ b/scripts/DeploySavings.s.sol @@ -30,15 +30,15 @@ contract DeploySavings is Utils { string memory jsonVanity = vm.readFile(JSON_VANITY_PATH); bytes32 salt = jsonVanity.readBytes32(string.concat("$.", "salt")); // Setup addresses - /* + address proxyAdmin = _chainToContract(chainId, ContractType.ProxyAdmin); address stablecoin = _chainToContract(chainId, ContractType.AgEUR); address coreBorrow = _chainToContract(chainId, ContractType.CoreBorrow); - */ - + /* address proxyAdmin = 0x1D941EF0D3Bba4ad67DBfBCeE5262F4CEE53A32b; address stablecoin = 0x1a7e4e63778B4f12a199C062f3eFdD288afCBce8; address coreBorrow = 0x4b1E2c2762667331Bc91648052F646d1b0d35984; + */ console.log(proxyAdmin, stablecoin, coreBorrow); vm.startBroadcast(deployerPrivateKey); @@ -56,7 +56,7 @@ contract DeploySavings is Utils { // initCode on stEUR bytes memory initCode = hex"60406080815262000cdb80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a3575016179055516108869081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe60806040526004361015610019575b3661037c575b61037c565b6000803560e01c9081633659cfe61461006c575080634f1ef286146100675780635c60da1b146100625780638f2839701461005d5763f851a4400361000e57610326565b610212565b61019e565b6100ef565b346100d15760203660031901126100d1576100856100d4565b6001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014576100ce906100c56104af565b90838252610516565b80f35b80fd5b600435906001600160a01b03821682036100ea57565b600080fd5b60403660031901126100ea576101036100d4565b60243567ffffffffffffffff918282116100ea57366023830112156100ea5781600401359283116100ea5736602484840101116100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461001457600060208480602461018a61018561019c996104fa565b6104d4565b96828852018387013784010152610624565b005b346100ea5760003660031901126100ea576001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610354163314600014610014577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460405191168152602090f35b346100ea5760203660031901126100ea5761022b6100d4565b6001600160a01b03907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610014577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156102bc5773ffffffffffffffffffffffffffffffffffffffff1916179055005b608460405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b346100ea5760003660031901126100ea576001600160a01b037fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039080825416331460001461001457905460405191168152602090f35b6001600160a01b03807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146103f0577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e156103ec573d90f35b3d90fd5b60a460405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff8211176104cf57604052565b610480565b6040519190601f01601f1916820167ffffffffffffffff8111838210176104cf57604052565b67ffffffffffffffff81116104cf57601f01601f191660200190565b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906105b2575b61059d575050565b6105af916105a96106b2565b91610722565b50565b506000610595565b608460405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156105ba576001600160a01b0381167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8173ffffffffffffffffffffffffffffffffffffffff198254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906106aa5761059d575050565b506001610595565b604051906060820182811067ffffffffffffffff8211176104cf57604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b9190823b1561076b576000816107609460208394519201905af43d15610763573d90610750610185836104fa565b9182523d6000602084013e6107d5565b90565b6060906107d5565b608460405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b909190156107e1575090565b8151156107f15750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510610837575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061081456fea2646970667358221220cd51d87f687e65d41171d5f157313805c8c9f1c12984f1d6b0d726a20f3df98e64736f6c634300081300330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"; - + /* console.log("Proxy bytecode"); console.logBytes(initCode); console.log(""); @@ -81,6 +81,7 @@ contract DeploySavings is Utils { "stEUR", 1 ); + */ vm.stopBroadcast(); } diff --git a/scripts/DeploySavingsImplem.s.sol b/scripts/DeploySavingsImplem.s.sol new file mode 100644 index 00000000..363d33d5 --- /dev/null +++ b/scripts/DeploySavingsImplem.s.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import "./utils/Utils.s.sol"; +import "utils/src/Constants.sol"; +import { console } from "forge-std/console.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import "stringutils/strings.sol"; +import { SavingsNameable } from "contracts/savings/nameable/SavingsNameable.sol"; +import { IAccessControlManager } from "contracts/utils/AccessControl.sol"; +import "oz/interfaces/IERC20.sol"; +import "oz-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; + +import { ImmutableCreate2Factory } from "./utils/TransmuterDeploymentHelper.s.sol"; + +contract DeploySavingsImplem is Utils { + using stdJson for string; + using strings for *; + + function run() external { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + ImmutableCreate2Factory create2Factory = ImmutableCreate2Factory(IMMUTABLE_CREATE2_FACTORY_ADDRESS); + bytes32 salt = 0xa9ddd91249dfdd450e81e1c56ab60e1a62651701000000000000000000438ec0; + + vm.startBroadcast(deployerPrivateKey); + + address deployer = vm.addr(deployerPrivateKey); + console.log("Deployer address: ", deployer); + + bytes memory emptyData; + bytes memory initCode = abi.encodePacked( + type(SavingsNameable).creationCode, + abi.encode(IMMUTABLE_CREATE2_FACTORY_ADDRESS, deployer, emptyData) + ); + address computedAddress = create2Factory.findCreate2Address(salt, initCode); + console.log("Supposed to deploy: %s", computedAddress); + if (computedAddress != 0x2C28Bd22aB59341892e85aD76d159d127c4B03FA) revert(); + /* + address saving = create2Factory.safeCreate2(salt, initCode); + console.log("Savings implementation deployed at: ", address(saving)); + */ + + vm.stopBroadcast(); + } +} diff --git a/scripts/DeployTransmuterWithoutFacets.s.sol b/scripts/DeployTransmuterWithoutFacets.s.sol index 98f6044c..7ddf30f1 100644 --- a/scripts/DeployTransmuterWithoutFacets.s.sol +++ b/scripts/DeployTransmuterWithoutFacets.s.sol @@ -24,7 +24,6 @@ import { Swapper } from "contracts/transmuter/facets/Swapper.sol"; import { ITransmuter } from "interfaces/ITransmuter.sol"; import { MockTreasury } from "test/mock/MockTreasury.sol"; -import { MockToken } from "borrow/mock/MockToken.sol"; contract DeployTransmuterWithoutFacets is TransmuterDeploymentHelper { function run() external { diff --git a/scripts/gnosis/DeploySavingsGnosis.s.sol b/scripts/gnosis/DeploySavingsGnosis.s.sol index 8c6ffe73..5ac11f5c 100644 --- a/scripts/gnosis/DeploySavingsGnosis.s.sol +++ b/scripts/gnosis/DeploySavingsGnosis.s.sol @@ -10,7 +10,7 @@ import { AccessControl, IAccessControlManager } from "contracts/utils/AccessCont import { MockTokenPermit } from "test/mock/MockTokenPermit.sol"; import "oz/interfaces/IERC20.sol"; import "oz-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import "borrow/external/ProxyAdmin.sol"; +import "oz/proxy/transparent/ProxyAdmin.sol"; contract DeploySavingsGnosis is Utils { using strings for *; diff --git a/scripts/gnosis/DeployTransmuterGnosis.s.sol b/scripts/gnosis/DeployTransmuterGnosis.s.sol index cf1e85e2..dc0b532d 100644 --- a/scripts/gnosis/DeployTransmuterGnosis.s.sol +++ b/scripts/gnosis/DeployTransmuterGnosis.s.sol @@ -10,7 +10,7 @@ import { CollateralSetupProd, FakeGnosis } from "contracts/transmuter/configs/Fa import "contracts/transmuter/Storage.sol" as Storage; import { ITransmuter } from "interfaces/ITransmuter.sol"; import { MockTokenPermit } from "test/mock/MockTokenPermit.sol"; -import { MockCoreBorrow } from "borrow/mock/MockCoreBorrow.sol"; +import { MockAccessControlManager } from "test/mock/MockAccessControlManager.sol"; import { DummyDiamondImplementation } from "../generated/DummyDiamondImplementation.sol"; import { MockChainlinkOracle } from "test/mock/MockChainlinkOracle.sol"; @@ -30,7 +30,7 @@ contract DeployTransmuterGnosis is TransmuterDeploymentHelper { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ // Deploy fakes Core borrow, agEUR, and collaterals - MockCoreBorrow coreBorrow = new MockCoreBorrow(); + MockAccessControlManager coreBorrow = new MockAccessControlManager(); console.log("CoreBorrow deployed at: %s", address(coreBorrow)); coreBorrow.toggleGovernor(deployer); diff --git a/test/fuzz/Harvester.t.sol b/test/fuzz/Harvester.t.sol index 13244b5c..6164649b 100644 --- a/test/fuzz/Harvester.t.sol +++ b/test/fuzz/Harvester.t.sol @@ -169,14 +169,14 @@ contract HarvesterTest is Fixture, FunctionUtils { targetExposure + 10, minExposureYieldAsset - 1, maxExposureYieldAsset + 1, - 0 + 1 ); (address vault, uint64 target, uint64 maxi, uint64 mini, uint64 overrideExp) = harvester.collateralData(collat); assertEq(vault, address(_saving)); assertEq(target, targetExposure + 10); assertEq(maxi, maxExposureYieldAsset + 1); assertEq(mini, minExposureYieldAsset - 1); - assertEq(overrideExp, 2); + assertEq(overrideExp, 1); harvester.setCollateralData( address(_saving), From 7e412ed71de87fb684d76b92fa117f4bee79f72b Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 18:52:38 +0200 Subject: [PATCH 16/20] feat: update dash --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 50213425..1d97a70a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,11 +12,11 @@ ignore = dirty [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable - url = https://github.com//OpenZeppelin/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable version = v4.7.3 [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts - url = https://github.com//OpenZeppelin/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts version = v4.7.3 [submodule "lib/utils"] path = lib/utils From 5bdc8b41f82ade2c31b3aad5ea614f2ca6eda99d Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Mon, 27 May 2024 18:54:03 +0200 Subject: [PATCH 17/20] delete useless script --- scripts/DeploySavingsNameable.sol | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 scripts/DeploySavingsNameable.sol diff --git a/scripts/DeploySavingsNameable.sol b/scripts/DeploySavingsNameable.sol deleted file mode 100644 index 3ba7b5cb..00000000 --- a/scripts/DeploySavingsNameable.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; - -import "./utils/Utils.s.sol"; -import "utils/src/Constants.sol"; -import { console } from "forge-std/console.sol"; -import "oz/interfaces/IERC20.sol"; -import { SavingsNameable } from "contracts/savings/nameable/SavingsNameable.sol"; - -/// @dev To deploy on a different chain, just replace the chainId and be sure the sdk has the required addresses -contract DeploySavingsNameable is Utils { - function run() external { - uint256 privateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - vm.startBroadcast(privateKey); - address executor = vm.addr(privateKey); - vm.label(executor, "Executor"); - SavingsNameable savingsImpl = new SavingsNameable(); - console.log("New Savings Implementation deployed at: ", address(savingsImpl)); - vm.stopBroadcast(); - } -} From 5e00c5958668da89a50947f03c6d69ed601adef1 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Wed, 29 May 2024 15:00:16 +0200 Subject: [PATCH 18/20] fix: harvester code --- contracts/helpers/Harvester.sol | 9 +++++-- scripts/DeployHarvester.s.sol | 26 +++++++++++++++++++ scripts/DeploySavingsImplem.s.sol | 3 +-- test/scripts/HarvesterUSDATest.t.sol | 2 ++ .../upgrade/SavingsNameableUpgradeTest.t.sol | 23 ++++++++-------- 5 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 scripts/DeployHarvester.s.sol diff --git a/contracts/helpers/Harvester.sol b/contracts/helpers/Harvester.sol index 0f62da42..96788642 100644 --- a/contracts/helpers/Harvester.sol +++ b/contracts/helpers/Harvester.sol @@ -71,11 +71,14 @@ contract Harvester is AccessControl { HARVEST //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ - /// @notice Invests or divests from the yield asset associated to `collateral` + /// @notice Invests or divests from the yield asset associated to `collateral` based on the current exposure to this + /// collateral /// @dev This transaction either reduces the exposure to `collateral` in the Transmuter or frees up some collateral - /// that can then be used for people looking to burn + /// that can then be used for people looking to burn stablecoins /// @dev Due to potential transaction fees within the Transmuter, this function doesn't exactly bring `collateral` /// to the target exposure + /// @dev The `harvest` possibility shouldn't be implemented for assets with a manipulable price (like ERC4626) + /// contracts on which the `previewRedeem` values can be easily moved by creating a loss or a profit function harvest(address collateral) external { (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = TRANSMUTER.getIssuedByCollateral(collateral); CollatParams memory collatInfo = collateralData[collateral]; @@ -124,6 +127,8 @@ contract Harvester is AccessControl { rebalancer = RebalancerFlashloan(_newRebalancer); } + /// @dev This function shouldn't be called for a vault (e.g an ERC4626 token) which price can be easily moved + /// by creating a loss or a profit, at the risk of depleting the reserves available in the Rebalancer function setCollateralData( address vault, uint64 targetExposure, diff --git a/scripts/DeployHarvester.s.sol b/scripts/DeployHarvester.s.sol new file mode 100644 index 00000000..ea5a0a1f --- /dev/null +++ b/scripts/DeployHarvester.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import "./utils/Utils.s.sol"; +import { console } from "forge-std/console.sol"; +import { Harvester } from "contracts/helpers/Harvester.sol"; +import "./Constants.s.sol"; + +contract DeployHarvester is Utils { + function run() external { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + address deployer = vm.addr(deployerPrivateKey); + console.log("Deployer address: ", deployer); + address rebalancer = 0x22604C0E5633A9810E01c9cb469B23Eee17AC411; + address vault = STEAK_USDC; + uint64 targetExposure = (13 * 1e9) / 100; + uint64 overrideExposures = 0; + uint96 maxSlippage = 1e9 / 100; + Harvester harvester = new Harvester(rebalancer, vault, targetExposure, overrideExposures, 0, 0, maxSlippage); + console.log("Harvester deployed at: ", address(harvester)); + + vm.stopBroadcast(); + } +} diff --git a/scripts/DeploySavingsImplem.s.sol b/scripts/DeploySavingsImplem.s.sol index 363d33d5..d69a8b9d 100644 --- a/scripts/DeploySavingsImplem.s.sol +++ b/scripts/DeploySavingsImplem.s.sol @@ -35,10 +35,9 @@ contract DeploySavingsImplem is Utils { address computedAddress = create2Factory.findCreate2Address(salt, initCode); console.log("Supposed to deploy: %s", computedAddress); if (computedAddress != 0x2C28Bd22aB59341892e85aD76d159d127c4B03FA) revert(); - /* + address saving = create2Factory.safeCreate2(salt, initCode); console.log("Savings implementation deployed at: ", address(saving)); - */ vm.stopBroadcast(); } diff --git a/test/scripts/HarvesterUSDATest.t.sol b/test/scripts/HarvesterUSDATest.t.sol index ac7ddff7..d665f3ac 100644 --- a/test/scripts/HarvesterUSDATest.t.sol +++ b/test/scripts/HarvesterUSDATest.t.sol @@ -82,6 +82,7 @@ contract HarvesterUSDATest is Test { assertGt(fromSTEAK, fromSTEAK2); assertGt(total, total2); assertApproxEqRel((fromUSDC2 * 1e9) / total2, targetExposure, 100 * BPS); + assertApproxEqRel(fromUSDC2 * 1e9, targetExposure * total, 100 * BPS); harvester.harvest(USDC); (uint256 fromUSDC3, uint256 total3) = transmuter.getIssuedByCollateral(address(USDC)); @@ -90,6 +91,7 @@ contract HarvesterUSDATest is Test { assertGt(fromSTEAK2, fromSTEAK3); assertGt(total2, total3); assertGt((fromUSDC3 * 1e9) / total3, (fromUSDC2 * 1e9) / total2); + assertApproxEqRel((fromUSDC3 * 1e9) / total3, (fromUSDC2 * 1e9) / total2, 10 * BPS); assertGt(targetExposure, (fromUSDC3 * 1e9) / total3); } diff --git a/test/units/upgrade/SavingsNameableUpgradeTest.t.sol b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol index b0cbff4f..af456f77 100644 --- a/test/units/upgrade/SavingsNameableUpgradeTest.t.sol +++ b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol @@ -10,8 +10,8 @@ import { IERC20Metadata } from "oz/interfaces/IERC20Metadata.sol"; import { TransparentUpgradeableProxy } from "oz/proxy/transparent/TransparentUpgradeableProxy.sol"; contract SavingsNameableUpgradeTest is Test, Helper { - uint256 constant CHAIN = CHAIN_ETHEREUM; - string constant CHAIN_NAME = "mainnet"; + uint256 constant CHAIN = CHAIN_POLYGONZKEVM; + string constant CHAIN_NAME = "polygonzkevm"; address public savings; address public savingsImpl; @@ -42,13 +42,14 @@ contract SavingsNameableUpgradeTest is Test, Helper { guardian = _chainToContract(CHAIN, ContractType.GuardianMultisig); // TODO: to be removed when chainToContract works - savings = 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776; + // savings = 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776; + savings = 0x004626A008B1aCdC4c74ab51644093b155e59A23; proxyAdmin = ProxyAdmin(0x1D941EF0D3Bba4ad67DBfBCeE5262F4CEE53A32b); - governor = 0xdC4e6DFe07EFCa50a197DF15D9200883eF4Eb1c8; - guardian = 0x0C2553e4B9dFA9f83b1A6D3EAB96c4bAaB42d430; + governor = 0x2a42Aeec7519883713272ec10FE44461a2Dfe354; + guardian = 0x10DeF8a92c51C8082087356186a1485301078DCd; - assertEq(IERC20Metadata(savings).name(), "Staked USDA"); - assertEq(IERC20Metadata(savings).symbol(), "stUSD"); + assertEq(IERC20Metadata(savings).name(), "Staked EURA"); + assertEq(IERC20Metadata(savings).symbol(), "stEUR"); rate = SavingsNameable(savings).rate(); lastUpdate = SavingsNameable(savings).lastUpdate(); paused = SavingsNameable(savings).paused(); @@ -60,19 +61,19 @@ contract SavingsNameableUpgradeTest is Test, Helper { previewWithdraw = SavingsNameable(savings).previewWithdraw(BASE_18); previewRedeem = SavingsNameable(savings).previewRedeem(BASE_18); - savingsImpl = address(new SavingsNameable()); - _upgradeContract("Staked USDA", "stUSD"); + // savingsImpl = address(new SavingsNameable()); + savingsImpl = 0x2C28Bd22aB59341892e85aD76d159d127c4B03FA; } function _upgradeContract(string memory name, string memory symbol) internal { - if (CHAIN == CHAIN_BASE || CHAIN == CHAIN_POLYGONZKEVM) vm.prank(guardian, guardian); - else vm.prank(governor, governor); + vm.prank(governor, governor); proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(savings)), savingsImpl); vm.prank(governor, governor); SavingsNameable(savings).setNameAndSymbol(name, symbol); } function test_UpdatedValues() public { + _upgradeContract("Staked USDA", "stUSD"); assertEq(IERC20Metadata(savings).name(), "Staked USDA"); assertEq(IERC20Metadata(savings).symbol(), "stUSD"); assertEq(SavingsNameable(savings).previewRedeem(BASE_18), previewRedeem); From cf2a31867eeaa05b073f104738a8d18d3a488744 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Fri, 31 May 2024 13:58:47 +0200 Subject: [PATCH 19/20] feat: update transmuter --- test/units/upgrade/SavingsNameableUpgradeTest.t.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/units/upgrade/SavingsNameableUpgradeTest.t.sol b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol index af456f77..238125f1 100644 --- a/test/units/upgrade/SavingsNameableUpgradeTest.t.sol +++ b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol @@ -10,8 +10,8 @@ import { IERC20Metadata } from "oz/interfaces/IERC20Metadata.sol"; import { TransparentUpgradeableProxy } from "oz/proxy/transparent/TransparentUpgradeableProxy.sol"; contract SavingsNameableUpgradeTest is Test, Helper { - uint256 constant CHAIN = CHAIN_POLYGONZKEVM; - string constant CHAIN_NAME = "polygonzkevm"; + uint256 constant CHAIN = CHAIN_BASE; + string constant CHAIN_NAME = "base"; address public savings; address public savingsImpl; @@ -44,9 +44,9 @@ contract SavingsNameableUpgradeTest is Test, Helper { // TODO: to be removed when chainToContract works // savings = 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776; savings = 0x004626A008B1aCdC4c74ab51644093b155e59A23; - proxyAdmin = ProxyAdmin(0x1D941EF0D3Bba4ad67DBfBCeE5262F4CEE53A32b); - governor = 0x2a42Aeec7519883713272ec10FE44461a2Dfe354; - guardian = 0x10DeF8a92c51C8082087356186a1485301078DCd; + proxyAdmin = ProxyAdmin(0x31429d1856aD1377A8A0079410B297e1a9e214c2); + governor = 0x7DF37fc774843b678f586D55483819605228a0ae; + guardian = 0xe4BB74804edf5280c9203f034036f7CB15196078; assertEq(IERC20Metadata(savings).name(), "Staked EURA"); assertEq(IERC20Metadata(savings).symbol(), "stEUR"); From f90246ebc9f3bd067452e30a29ea0d23971364f2 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Fri, 31 May 2024 14:03:19 +0200 Subject: [PATCH 20/20] feat: update with ETH --- .github/workflows/ci-deep.yml | 2 ++ .github/workflows/ci.yml | 3 +++ test/units/upgrade/SavingsNameableUpgradeTest.t.sol | 10 +++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-deep.yml b/.github/workflows/ci-deep.yml index 2c91b8e3..d6bc39ec 100644 --- a/.github/workflows/ci-deep.yml +++ b/.github/workflows/ci-deep.yml @@ -114,6 +114,7 @@ jobs: ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + ETH_NODE_URI_BASE: ${{ secrets.ETH_NODE_URI_BASE }} # test-invariant: # needs: ["build", "lint"] @@ -171,3 +172,4 @@ jobs: ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + ETH_NODE_URI_BASE: ${{ secrets.ETH_NODE_URI_BASE }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d4b80a9..b59af4ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,7 @@ jobs: ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + ETH_NODE_URI_BASE: ${{ secrets.ETH_NODE_URI_BASE }} # test-invariant: # needs: ["build", "lint"] @@ -160,6 +161,7 @@ jobs: ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + ETH_NODE_URI_BASE: ${{ secrets.ETH_NODE_URI_BASE }} coverage: needs: ["build", "lint"] @@ -191,6 +193,7 @@ jobs: ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + ETH_NODE_URI_BASE: ${{ secrets.ETH_NODE_URI_BASE }} - name: "Upload coverage report to Codecov" uses: "codecov/codecov-action@v3" diff --git a/test/units/upgrade/SavingsNameableUpgradeTest.t.sol b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol index 238125f1..12acdb7e 100644 --- a/test/units/upgrade/SavingsNameableUpgradeTest.t.sol +++ b/test/units/upgrade/SavingsNameableUpgradeTest.t.sol @@ -10,8 +10,8 @@ import { IERC20Metadata } from "oz/interfaces/IERC20Metadata.sol"; import { TransparentUpgradeableProxy } from "oz/proxy/transparent/TransparentUpgradeableProxy.sol"; contract SavingsNameableUpgradeTest is Test, Helper { - uint256 constant CHAIN = CHAIN_BASE; - string constant CHAIN_NAME = "base"; + uint256 constant CHAIN = CHAIN_ETHEREUM; + string constant CHAIN_NAME = "mainnet"; address public savings; address public savingsImpl; @@ -44,9 +44,9 @@ contract SavingsNameableUpgradeTest is Test, Helper { // TODO: to be removed when chainToContract works // savings = 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776; savings = 0x004626A008B1aCdC4c74ab51644093b155e59A23; - proxyAdmin = ProxyAdmin(0x31429d1856aD1377A8A0079410B297e1a9e214c2); - governor = 0x7DF37fc774843b678f586D55483819605228a0ae; - guardian = 0xe4BB74804edf5280c9203f034036f7CB15196078; + proxyAdmin = ProxyAdmin(0x1D941EF0D3Bba4ad67DBfBCeE5262F4CEE53A32b); + governor = 0xdC4e6DFe07EFCa50a197DF15D9200883eF4Eb1c8; + guardian = 0x0C2553e4B9dFA9f83b1A6D3EAB96c4bAaB42d430; assertEq(IERC20Metadata(savings).name(), "Staked EURA"); assertEq(IERC20Metadata(savings).symbol(), "stEUR");