diff --git a/.gitignore b/.gitignore index 8bd15c67..b2485635 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ out/ broadcast node_modules package-lock.json -.DS_Store \ No newline at end of file +.DS_Store + +yarn-error.log \ No newline at end of file diff --git a/package.json b/package.json index 80efeef3..12ab9049 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,10 @@ }, "homepage": "https://github.com/bgd-labs/aave-helpers#readme", "devDependencies": { - "prettier": "^2.7.1", - "prettier-plugin-solidity": "^1.0.0-beta.19" + "prettier": "^2.8.3", + "prettier-plugin-solidity": "^1.1.3" }, "dependencies": { - "@bgd-labs/report-engine": "^0.0.9", - "diffler": "^2.0.4", - "isomorphic-unfetch": "^4.0.2", - "node-fetch": "^3.3.1", - "zod": "^3.21.4" + "@bgd-labs/report-engine": "^0.0.9" } } diff --git a/reports/postTestEngineOptV3.json b/reports/postTestEngineOptV3.json index a59e23c3..df6b1bc6 100644 --- a/reports/postTestEngineOptV3.json +++ b/reports/postTestEngineOptV3.json @@ -41,7 +41,7 @@ "oracle": "0x698B585CbC4407e2D54aa898B2600B53C68958f7", "oracleDecimals": 8, "oracleDescription": "WSTETH / USD", - "oracleLatestAnswer": 174300712435, + "oracleLatestAnswer": 172290174000, "reserveFactor": 1500, "stableBorrowRateEnabled": false, "stableDebtToken": "0x78246294a4c6fBf614Ed73CcC9F8b875ca8eE841", @@ -74,7 +74,7 @@ "oracle": "0xCc232dcFAAE6354cE191Bd574108c1aD03f86450", "oracleDecimals": 8, "oracleDescription": "LINK / USD", - "oracleLatestAnswer": 691711677, + "oracleLatestAnswer": 671559920, "reserveFactor": 2000, "stableBorrowRateEnabled": false, "stableDebtToken": "0x89D976629b7055ff1ca02b927BA3e020F22A44e4", @@ -107,7 +107,7 @@ "oracle": "0x13e3Ee699D1909E989722E753853AE30b17e08c5", "oracleDecimals": 8, "oracleDescription": "ETH / USD", - "oracleLatestAnswer": 157141000000, + "oracleLatestAnswer": 154457000000, "reserveFactor": 1500, "stableBorrowRateEnabled": false, "stableDebtToken": "0xD8Ad37849950903571df17049516a5CD4cbE55F6", @@ -140,7 +140,7 @@ "oracle": "0x0D276FC14719f9292D5C1eA2198673d1f4269246", "oracleDecimals": 8, "oracleDescription": "OP / USD", - "oracleLatestAnswer": 254511663, + "oracleLatestAnswer": 236454140, "reserveFactor": 2000, "stableBorrowRateEnabled": false, "stableDebtToken": "0x08Cb71192985E936C7Cd166A8b268035e400c3c3", @@ -173,7 +173,7 @@ "oracle": "0xD702DD976Fb76Fffc2D3963D037dfDae5b04E593", "oracleDecimals": 8, "oracleDescription": "BTC / USD", - "oracleLatestAnswer": 2245475153319, + "oracleLatestAnswer": 2205261790595, "reserveFactor": 2000, "stableBorrowRateEnabled": false, "stableDebtToken": "0x633b207Dd676331c413D4C013a6294B0FE47cD0e", @@ -206,7 +206,7 @@ "oracle": "0x338ed6787f463394D24813b297401B9F05a8C9d1", "oracleDecimals": 8, "oracleDescription": "AAVE / USD", - "oracleLatestAnswer": 7665000000, + "oracleLatestAnswer": 7425000000, "reserveFactor": 0, "stableBorrowRateEnabled": false, "stableDebtToken": "0xfAeF6A702D15428E588d4C0614AEFb4348D83D48", @@ -272,7 +272,7 @@ "oracle": "0x7f99817d87baD03ea21E05112Ca799d715730efe", "oracleDecimals": 8, "oracleDescription": "SUSD / USD", - "oracleLatestAnswer": 100164737, + "oracleLatestAnswer": 100241227, "reserveFactor": 1000, "stableBorrowRateEnabled": false, "stableDebtToken": "0xF15F26710c827DDe8ACBA678682F3Ce24f2Fb56E", @@ -305,7 +305,7 @@ "oracle": "0xECef79E109e997bCA29c1c0897ec9d7b03647F5E", "oracleDecimals": 8, "oracleDescription": "USDT / USD", - "oracleLatestAnswer": 99995128, + "oracleLatestAnswer": 99991000, "reserveFactor": 1000, "stableBorrowRateEnabled": true, "stableDebtToken": "0x70eFfc565DB6EEf7B927610155602d31b670e802", @@ -338,7 +338,7 @@ "oracle": "0x8dBa75e83DA73cc766A7e5a0ee71F656BAb470d6", "oracleDecimals": 8, "oracleDescription": "DAI / USD", - "oracleLatestAnswer": 99979000, + "oracleLatestAnswer": 99990000, "reserveFactor": 1000, "stableBorrowRateEnabled": true, "stableDebtToken": "0xd94112B5B62d53C9402e7A60289c6810dEF1dC9B", diff --git a/reports/preTestEngineOptV3.json b/reports/preTestEngineOptV3.json index dfe1e417..4625d3b5 100644 --- a/reports/preTestEngineOptV3.json +++ b/reports/preTestEngineOptV3.json @@ -41,7 +41,7 @@ "oracle": "0x698B585CbC4407e2D54aa898B2600B53C68958f7", "oracleDecimals": 8, "oracleDescription": "WSTETH / USD", - "oracleLatestAnswer": 174300712435, + "oracleLatestAnswer": 172290174000, "reserveFactor": 1500, "stableBorrowRateEnabled": false, "stableDebtToken": "0x78246294a4c6fBf614Ed73CcC9F8b875ca8eE841", @@ -74,7 +74,7 @@ "oracle": "0xCc232dcFAAE6354cE191Bd574108c1aD03f86450", "oracleDecimals": 8, "oracleDescription": "LINK / USD", - "oracleLatestAnswer": 691711677, + "oracleLatestAnswer": 671559920, "reserveFactor": 2000, "stableBorrowRateEnabled": false, "stableDebtToken": "0x89D976629b7055ff1ca02b927BA3e020F22A44e4", @@ -107,7 +107,7 @@ "oracle": "0x13e3Ee699D1909E989722E753853AE30b17e08c5", "oracleDecimals": 8, "oracleDescription": "ETH / USD", - "oracleLatestAnswer": 157141000000, + "oracleLatestAnswer": 154457000000, "reserveFactor": 1000, "stableBorrowRateEnabled": false, "stableDebtToken": "0xD8Ad37849950903571df17049516a5CD4cbE55F6", @@ -140,7 +140,7 @@ "oracle": "0x0D276FC14719f9292D5C1eA2198673d1f4269246", "oracleDecimals": 8, "oracleDescription": "OP / USD", - "oracleLatestAnswer": 254511663, + "oracleLatestAnswer": 236454140, "reserveFactor": 2000, "stableBorrowRateEnabled": false, "stableDebtToken": "0x08Cb71192985E936C7Cd166A8b268035e400c3c3", @@ -173,7 +173,7 @@ "oracle": "0xD702DD976Fb76Fffc2D3963D037dfDae5b04E593", "oracleDecimals": 8, "oracleDescription": "BTC / USD", - "oracleLatestAnswer": 2245475153319, + "oracleLatestAnswer": 2205261790595, "reserveFactor": 2000, "stableBorrowRateEnabled": false, "stableDebtToken": "0x633b207Dd676331c413D4C013a6294B0FE47cD0e", @@ -206,7 +206,7 @@ "oracle": "0x338ed6787f463394D24813b297401B9F05a8C9d1", "oracleDecimals": 8, "oracleDescription": "AAVE / USD", - "oracleLatestAnswer": 7665000000, + "oracleLatestAnswer": 7425000000, "reserveFactor": 0, "stableBorrowRateEnabled": false, "stableDebtToken": "0xfAeF6A702D15428E588d4C0614AEFb4348D83D48", @@ -272,7 +272,7 @@ "oracle": "0x7f99817d87baD03ea21E05112Ca799d715730efe", "oracleDecimals": 8, "oracleDescription": "SUSD / USD", - "oracleLatestAnswer": 100164737, + "oracleLatestAnswer": 100241227, "reserveFactor": 1000, "stableBorrowRateEnabled": false, "stableDebtToken": "0xF15F26710c827DDe8ACBA678682F3Ce24f2Fb56E", @@ -305,7 +305,7 @@ "oracle": "0xECef79E109e997bCA29c1c0897ec9d7b03647F5E", "oracleDecimals": 8, "oracleDescription": "USDT / USD", - "oracleLatestAnswer": 99995128, + "oracleLatestAnswer": 99991000, "reserveFactor": 1000, "stableBorrowRateEnabled": true, "stableDebtToken": "0x70eFfc565DB6EEf7B927610155602d31b670e802", @@ -338,7 +338,7 @@ "oracle": "0x8dBa75e83DA73cc766A7e5a0ee71F656BAb470d6", "oracleDecimals": 8, "oracleDescription": "DAI / USD", - "oracleLatestAnswer": 99979000, + "oracleLatestAnswer": 99990000, "reserveFactor": 1000, "stableBorrowRateEnabled": true, "stableDebtToken": "0xd94112B5B62d53C9402e7A60289c6810dEF1dC9B", diff --git a/src/ProtocolV2TestBase.sol b/src/ProtocolV2TestBase.sol index 601793ec..726ff0cd 100644 --- a/src/ProtocolV2TestBase.sol +++ b/src/ProtocolV2TestBase.sol @@ -50,10 +50,10 @@ contract ProtocolV2TestBase is CommonTestBase { * @param pool the pool to be snapshotted * @return ReserveConfig[] list of configs */ - function createConfigurationSnapshot( - string memory reportName, - ILendingPool pool - ) public returns (ReserveConfig[] memory) { + function createConfigurationSnapshot(string memory reportName, ILendingPool pool) + public + returns (ReserveConfig[] memory) + { string memory path = string(abi.encodePacked('./reports/', reportName, '.json')); vm.writeFile(path, '{ "reserves": {}, "strategies": {}, "poolConfiguration": {} }'); vm.serializeUint('root', 'chainId', block.chainid); @@ -89,9 +89,11 @@ contract ProtocolV2TestBase is CommonTestBase { /** * @dev returns the first collateral in the list that cannot be borrowed in stable mode */ - function _getFirstCollateral( - ReserveConfig[] memory configs - ) private pure returns (ReserveConfig memory config) { + function _getFirstCollateral(ReserveConfig[] memory configs) + private + pure + returns (ReserveConfig memory config) + { for (uint256 i = 0; i < configs.length; i++) { if (configs[i].usageAsCollateralEnabled && !configs[i].stableBorrowRateEnabled) return configs[i]; @@ -109,7 +111,7 @@ contract ProtocolV2TestBase is CommonTestBase { ) internal { // test all basic interactions for (uint256 i = 0; i < configs.length; i++) { - uint256 amount = 100 * 10 ** configs[i].decimals; + uint256 amount = 100 * 10**configs[i].decimals; if (!configs[i].isFrozen) { _deposit(configs[i], pool, user, amount); _skipBlocks(1000); @@ -135,7 +137,7 @@ contract ProtocolV2TestBase is CommonTestBase { ReserveConfig memory collateralConfig = _getFirstCollateral(configs); _deposit(collateralConfig, pool, user, 1000000 ether); for (uint256 i = 0; i < configs.length; i++) { - uint256 amount = 10 ** configs[i].decimals; + uint256 amount = 10**configs[i].decimals; if (configs[i].borrowingEnabled) { _deposit(configs[i], pool, EOA, amount * 2); this._borrow(configs[i], pool, user, amount, false); @@ -157,7 +159,7 @@ contract ProtocolV2TestBase is CommonTestBase { ReserveConfig memory collateralConfig = _getFirstCollateral(configs); _deposit(collateralConfig, pool, user, 1000000 ether); for (uint256 i = 0; i < configs.length; i++) { - uint256 amount = 10 ** configs[i].decimals; + uint256 amount = 10**configs[i].decimals; if (configs[i].borrowingEnabled && configs[i].stableBorrowRateEnabled) { _deposit(configs[i], pool, EOA, amount * 2); this._borrow(configs[i], pool, user, amount, true); @@ -441,10 +443,11 @@ contract ProtocolV2TestBase is CommonTestBase { return vars.configs; } - function _getStructReserveTokens( - IAaveProtocolDataProvider pdp, - address underlyingAddress - ) internal view returns (ReserveTokens memory) { + function _getStructReserveTokens(IAaveProtocolDataProvider pdp, address underlyingAddress) + internal + view + returns (ReserveTokens memory) + { ReserveTokens memory reserveTokens; (reserveTokens.aToken, reserveTokens.stableDebtToken, reserveTokens.variableDebtToken) = pdp .getReserveTokensAddresses(underlyingAddress); @@ -512,10 +515,11 @@ contract ProtocolV2TestBase is CommonTestBase { }); } - function _findReserveConfig( - ReserveConfig[] memory configs, - address underlying - ) internal pure returns (ReserveConfig memory) { + function _findReserveConfig(ReserveConfig[] memory configs, address underlying) + internal + pure + returns (ReserveConfig memory) + { for (uint256 i = 0; i < configs.length; i++) { if (configs[i].underlying == underlying) { // Important to clone the struct, to avoid unexpected side effect if modifying the returned config @@ -704,10 +708,10 @@ contract ProtocolV2TestBase is CommonTestBase { } } - function _requireNoChangeInConfigs( - ReserveConfig memory config1, - ReserveConfig memory config2 - ) internal pure { + function _requireNoChangeInConfigs(ReserveConfig memory config1, ReserveConfig memory config2) + internal + pure + { require( keccak256(abi.encodePacked(config1.symbol)) == keccak256(abi.encodePacked(config2.symbol)), '_noReservesConfigsChangesApartNewListings() : UNEXPECTED_SYMBOL_CHANGED' diff --git a/src/ProtocolV3TestBase.sol b/src/ProtocolV3TestBase.sol index d8d975b5..9e92aa13 100644 --- a/src/ProtocolV3TestBase.sol +++ b/src/ProtocolV3TestBase.sol @@ -60,10 +60,10 @@ contract ProtocolV3TestBase is CommonTestBase { * @param pool the pool to be snapshotted * @return ReserveConfig[] list of configs */ - function createConfigurationSnapshot( - string memory reportName, - IPool pool - ) public returns (ReserveConfig[] memory) { + function createConfigurationSnapshot(string memory reportName, IPool pool) + public + returns (ReserveConfig[] memory) + { string memory path = string(abi.encodePacked('./reports/', reportName, '.json')); // overwrite with empty json to later be extended vm.writeFile( @@ -100,9 +100,11 @@ contract ProtocolV3TestBase is CommonTestBase { /** * @dev returns the first collateral in the list that cannot be borrowed in stable mode */ - function _getFirstCollateral( - ReserveConfig[] memory configs - ) private pure returns (ReserveConfig memory config) { + function _getFirstCollateral(ReserveConfig[] memory configs) + private + pure + returns (ReserveConfig memory config) + { for (uint256 i = 0; i < configs.length; i++) { if (configs[i].usageAsCollateralEnabled && !configs[i].stableBorrowRateEnabled) return configs[i]; @@ -113,10 +115,14 @@ contract ProtocolV3TestBase is CommonTestBase { /** * @dev tests that all assets can be deposited & withdrawn */ - function _supplyWithdrawFlow(ReserveConfig[] memory configs, IPool pool, address user) internal { + function _supplyWithdrawFlow( + ReserveConfig[] memory configs, + IPool pool, + address user + ) internal { // test all basic interactions for (uint256 i = 0; i < configs.length; i++) { - uint256 amount = 100 * 10 ** configs[i].decimals; + uint256 amount = 100 * 10**configs[i].decimals; if (!configs[i].isFrozen) { _deposit(configs[i], pool, user, amount); _skipBlocks(1000); @@ -133,12 +139,16 @@ contract ProtocolV3TestBase is CommonTestBase { /** * @dev tests that all assets with borrowing enabled can be borrowed */ - function _variableBorrowFlow(ReserveConfig[] memory configs, IPool pool, address user) internal { + function _variableBorrowFlow( + ReserveConfig[] memory configs, + IPool pool, + address user + ) internal { // put 1M whatever collateral, which should be enough to borrow 1 of each ReserveConfig memory collateralConfig = _getFirstCollateral(configs); _deposit(collateralConfig, pool, user, 1000000 ether); for (uint256 i = 0; i < configs.length; i++) { - uint256 amount = 10 ** configs[i].decimals; + uint256 amount = 10**configs[i].decimals; if (configs[i].borrowingEnabled) { _deposit(configs[i], pool, EOA, amount * 2); this._borrow(configs[i], pool, user, amount, false); @@ -151,12 +161,16 @@ contract ProtocolV3TestBase is CommonTestBase { /** * @dev tests that all assets with stable borrowing enabled can be borrowed */ - function _stableBorrowFlow(ReserveConfig[] memory configs, IPool pool, address user) internal { + function _stableBorrowFlow( + ReserveConfig[] memory configs, + IPool pool, + address user + ) internal { // put 1M whatever collateral, which should be enough to borrow 1 of each ReserveConfig memory collateralConfig = _getFirstCollateral(configs); _deposit(collateralConfig, pool, user, 1000000 ether); for (uint256 i = 0; i < configs.length; i++) { - uint256 amount = 10 ** configs[i].decimals; + uint256 amount = 10**configs[i].decimals; if (configs[i].borrowingEnabled && configs[i].stableBorrowRateEnabled) { _deposit(configs[i], pool, EOA, amount * 2); this._borrow(configs[i], pool, user, amount, true); @@ -474,10 +488,11 @@ contract ProtocolV3TestBase is CommonTestBase { return vars.configs; } - function _getStructReserveTokens( - IPoolDataProvider pdp, - address underlyingAddress - ) internal view returns (ReserveTokens memory) { + function _getStructReserveTokens(IPoolDataProvider pdp, address underlyingAddress) + internal + view + returns (ReserveTokens memory) + { ReserveTokens memory reserveTokens; (reserveTokens.aToken, reserveTokens.stableDebtToken, reserveTokens.variableDebtToken) = pdp .getReserveTokensAddresses(underlyingAddress); @@ -567,10 +582,11 @@ contract ProtocolV3TestBase is CommonTestBase { }); } - function _findReserveConfig( - ReserveConfig[] memory configs, - address underlying - ) internal pure returns (ReserveConfig memory) { + function _findReserveConfig(ReserveConfig[] memory configs, address underlying) + internal + pure + returns (ReserveConfig memory) + { for (uint256 i = 0; i < configs.length; i++) { if (configs[i].underlying == underlying) { // Important to clone the struct, to avoid unexpected side effect if modifying the returned config @@ -808,10 +824,10 @@ contract ProtocolV3TestBase is CommonTestBase { } } - function _requireNoChangeInConfigs( - ReserveConfig memory config1, - ReserveConfig memory config2 - ) internal pure { + function _requireNoChangeInConfigs(ReserveConfig memory config1, ReserveConfig memory config2) + internal + pure + { require( keccak256(abi.encodePacked(config1.symbol)) == keccak256(abi.encodePacked(config2.symbol)), '_noReservesConfigsChangesApartNewListings() : UNEXPECTED_SYMBOL_CHANGED' diff --git a/src/riskstewards/CapsPlusRiskSteward.sol b/src/riskstewards/CapsPlusRiskSteward.sol new file mode 100644 index 00000000..d14d6fd8 --- /dev/null +++ b/src/riskstewards/CapsPlusRiskSteward.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IACLManager, IPoolConfigurator, IPoolDataProvider} from 'aave-address-book/AaveV3.sol'; +import {Address} from 'solidity-utils/contracts/oz-common/Address.sol'; +import {EngineFlags} from '../v3-config-engine/EngineFlags.sol'; +import {IAaveV3ConfigEngine} from '../v3-config-engine/IAaveV3ConfigEngine.sol'; +import {ICapsPlusRiskSteward} from './ICapsPlusRiskSteward.sol'; + +/** + * @title CapsPlusRiskStewardErrors + * @author BGD labs + * @notice Library with all the potential errors to be thrown by the steward + */ +library CapsPlusRiskStewardErrors { + /** + * @notice Only the permissioned council is allowed to call methods on the steward. + */ + string public constant INVALID_CALLER = 'INVALID_CALLER'; + /** + * @notice The steward only allows cap increases. + */ + string public constant NOT_STRICTLY_HIGHER = 'NOT_STRICTLY_HIGHER'; + /** + * @notice A single cap can only be increased once every 5 days + */ + string public constant DEBOUNCE_NOT_RESPECTED = 'DEBOUNCE_NOT_RESPECTED'; + /** + * @notice A single cap increase must not increase the cap by more than 100% + */ + string public constant UPDATE_ABOVE_MAX = 'UPDATE_ABOVE_MAX'; + /** + * @notice There must be at least one cap update per execution + */ + string public constant NO_ZERO_UPDATES = 'NO_ZERO_UPDATES'; + /** + * @notice The steward does allow updates of caps, but not the initialization of non existing caps. + */ + string public constant NO_CAP_INITIALIZE = 'NO_CAP_INITIALIZE'; +} + +/** + * @title CapsPlusRiskSteward + * @author BGD labs + * @notice Contract managing caps increasing on an aave v3 pool + */ +contract CapsPlusRiskSteward is ICapsPlusRiskSteward { + using Address for address; + + /// @inheritdoc ICapsPlusRiskSteward + uint256 public constant MINIMUM_DELAY = 5 days; + + /// @inheritdoc ICapsPlusRiskSteward + IAaveV3ConfigEngine public immutable CONFIG_ENGINE; + + /// @inheritdoc ICapsPlusRiskSteward + IPoolDataProvider public immutable POOL_DATA_PROVIDER; + + /// @inheritdoc ICapsPlusRiskSteward + address public immutable RISK_COUNCIL; + + mapping(address => Debounce) internal _timelocks; + + /** + * @dev Modifier preventing anyone, but the council to update caps. + */ + modifier onlyRiskCouncil() { + require(RISK_COUNCIL == msg.sender, CapsPlusRiskStewardErrors.INVALID_CALLER); + _; + } + + /** + * @param poolDataProvider The pool data provider of the pool to be controlled by the steward + * @param engine the config engine to be used by the steward + * @param riskCouncil the safe address of the council being able to interact with the steward + */ + constructor( + IPoolDataProvider poolDataProvider, + IAaveV3ConfigEngine engine, + address riskCouncil + ) { + POOL_DATA_PROVIDER = poolDataProvider; + RISK_COUNCIL = riskCouncil; + CONFIG_ENGINE = engine; + } + + /// @inheritdoc ICapsPlusRiskSteward + function updateCaps(IAaveV3ConfigEngine.CapsUpdate[] calldata capUpdates) + external + onlyRiskCouncil + { + require(capUpdates.length > 0, CapsPlusRiskStewardErrors.NO_ZERO_UPDATES); + for (uint256 i = 0; i < capUpdates.length; i++) { + (uint256 currentBorrowCap, uint256 currentSupplyCap) = POOL_DATA_PROVIDER.getReserveCaps( + capUpdates[i].asset + ); + Debounce storage debounce = _timelocks[capUpdates[i].asset]; + if (capUpdates[i].supplyCap != EngineFlags.KEEP_CURRENT) { + _validateCapIncrease( + currentSupplyCap, + capUpdates[i].supplyCap, + debounce.supplyCapLastUpdated + ); + debounce.supplyCapLastUpdated = uint40(block.timestamp); + } + if (capUpdates[i].borrowCap != EngineFlags.KEEP_CURRENT) { + _validateCapIncrease( + currentBorrowCap, + capUpdates[i].borrowCap, + debounce.borrowCapLastUpdated + ); + debounce.borrowCapLastUpdated = uint40(block.timestamp); + } + } + address(CONFIG_ENGINE).functionDelegateCall( + abi.encodeWithSelector(CONFIG_ENGINE.updateCaps.selector, capUpdates) + ); + } + + /// @inheritdoc ICapsPlusRiskSteward + function getTimelock(address asset) external view returns (Debounce memory) { + return _timelocks[asset]; + } + + /** + * @notice A cap increase is valid, when it: + * - respects the debounce duration (5 day pause between updates must be respected) + * - the asset already had a cap (the steward can increase caps, but not initialize them) + * - the increase increases by a maximum of 100% of the current cap + * @param currentCap the current cap + * @param newCap the new cap + * @param lastUpdated the timestamp of the last update + */ + function _validateCapIncrease( + uint256 currentCap, + uint256 newCap, + uint40 lastUpdated + ) internal view { + require(currentCap != 0, CapsPlusRiskStewardErrors.NO_CAP_INITIALIZE); + require(newCap > currentCap, CapsPlusRiskStewardErrors.NOT_STRICTLY_HIGHER); + require( + block.timestamp - lastUpdated > MINIMUM_DELAY, + CapsPlusRiskStewardErrors.DEBOUNCE_NOT_RESPECTED + ); + require( + _capsIncreaseWithinAllowedRange(currentCap, newCap), + CapsPlusRiskStewardErrors.UPDATE_ABOVE_MAX + ); + } + + /** + * @notice Ensures the cap increase is within the allowed range. + * @param from current cap + * @param to new cap + * @return bool true, if difference is within the max 100% increase window + */ + function _capsIncreaseWithinAllowedRange(uint256 from, uint256 to) internal pure returns (bool) { + return to - from <= from; + } +} diff --git a/src/riskstewards/ICapsPlusRiskSteward.sol b/src/riskstewards/ICapsPlusRiskSteward.sol new file mode 100644 index 00000000..18c545fe --- /dev/null +++ b/src/riskstewards/ICapsPlusRiskSteward.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IACLManager, IPoolConfigurator, IPoolDataProvider} from 'aave-address-book/AaveV3.sol'; +import {Address} from 'solidity-utils/contracts/oz-common/Address.sol'; +import {EngineFlags} from '../v3-config-engine/EngineFlags.sol'; +import {IAaveV3ConfigEngine} from '../v3-config-engine/IAaveV3ConfigEngine.sol'; + +/** + * @title ICapsPlusRiskSteward + * @author BGD labs + * @notice Contract managing caps increasing on an aave v3 pool + */ +interface ICapsPlusRiskSteward { + /** + * @notice Stuct storing the last update of a specific cap + */ + struct Debounce { + uint40 supplyCapLastUpdated; + uint40 borrowCapLastUpdated; + } + + /** + * @notice The minimum delay that must be respected between updating a specific cap twice + */ + function MINIMUM_DELAY() external pure returns (uint256); + + /** + * @notice The config engine used to perform the cap update via delegatecall + */ + function CONFIG_ENGINE() external view returns (IAaveV3ConfigEngine); + + /** + * @notice The pool data provider of the POOL the steward controls + */ + function POOL_DATA_PROVIDER() external view returns (IPoolDataProvider); + + /** + * @notice The safe controlling the steward + */ + function RISK_COUNCIL() external view returns (address); + + /** + * @notice Allows increasing borrow and supply caps accross multiple assets + * @dev A cap increase is only possible ever 5 days per asset + * @dev A cap increase is only allowed to increase the cap by 50% + * @param capUpdates caps to be updated + */ + function updateCaps(IAaveV3ConfigEngine.CapsUpdate[] calldata capUpdates) external; + + /** + * @notice Returns the timelock for a specific asset + * @param asset for which to fetch the timelock + */ + function getTimelock(address asset) external view returns (Debounce memory); +} diff --git a/src/test/riskstewards/CapsPlusRiskSteward.t.sol b/src/test/riskstewards/CapsPlusRiskSteward.t.sol new file mode 100644 index 00000000..0dddad28 --- /dev/null +++ b/src/test/riskstewards/CapsPlusRiskSteward.t.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; +import {IACLManager, IPoolConfigurator, IPoolDataProvider} from 'aave-address-book/AaveV3.sol'; +import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {CapsPlusRiskSteward, CapsPlusRiskStewardErrors} from '../../riskstewards/CapsPlusRiskSteward.sol'; +import {IAaveV3ConfigEngine} from '../../v3-config-engine/IAaveV3ConfigEngine.sol'; +import {EngineFlags} from '../../v3-config-engine/EngineFlags.sol'; + +contract CapsPlusRiskSteward_Test is Test { + address public constant user = address(42); + CapsPlusRiskSteward public steward; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 17020741); + steward = new CapsPlusRiskSteward( + AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER, + IAaveV3ConfigEngine(AaveV3Ethereum.LISTING_ENGINE), + user + ); + vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + AaveV3Ethereum.ACL_MANAGER.addRiskAdmin(address(steward)); + vm.stopPrank(); + } + + function test_increaseCapsMax() public { + (uint256 daiBorrowCapBefore, uint256 daiSupplyCapBefore) = AaveV3Ethereum + .AAVE_PROTOCOL_DATA_PROVIDER + .getReserveCaps(AaveV3EthereumAssets.DAI_UNDERLYING); + + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + daiSupplyCapBefore * 2, + daiBorrowCapBefore * 2 + ); + + vm.startPrank(user); + steward.updateCaps(capUpdates); + + (uint256 daiBorrowCapAfter, uint256 daiSupplyCapAfter) = AaveV3Ethereum + .AAVE_PROTOCOL_DATA_PROVIDER + .getReserveCaps(AaveV3EthereumAssets.DAI_UNDERLYING); + + CapsPlusRiskSteward.Debounce memory lastUpdated = steward.getTimelock( + AaveV3EthereumAssets.DAI_UNDERLYING + ); + assertEq(daiBorrowCapAfter, capUpdates[0].borrowCap); + assertEq(daiSupplyCapAfter, capUpdates[0].supplyCap); + assertEq(lastUpdated.supplyCapLastUpdated, block.timestamp); + assertEq(lastUpdated.borrowCapLastUpdated, block.timestamp); + } + + function test_keepCurrent() public { + (uint256 daiBorrowCapBefore, uint256 daiSupplyCapBefore) = AaveV3Ethereum + .AAVE_PROTOCOL_DATA_PROVIDER + .getReserveCaps(AaveV3EthereumAssets.DAI_UNDERLYING); + + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + EngineFlags.KEEP_CURRENT, + EngineFlags.KEEP_CURRENT + ); + + vm.startPrank(user); + steward.updateCaps(capUpdates); + + (uint256 daiBorrowCapAfter, uint256 daiSupplyCapAfter) = AaveV3Ethereum + .AAVE_PROTOCOL_DATA_PROVIDER + .getReserveCaps(AaveV3EthereumAssets.DAI_UNDERLYING); + + CapsPlusRiskSteward.Debounce memory lastUpdated = steward.getTimelock( + AaveV3EthereumAssets.DAI_UNDERLYING + ); + assertEq(daiBorrowCapAfter, daiBorrowCapBefore); + assertEq(daiSupplyCapAfter, daiSupplyCapBefore); + assertEq(lastUpdated.supplyCapLastUpdated, 0); + assertEq(lastUpdated.borrowCapLastUpdated, 0); + } + + function test_invalidCaller() public { + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + EngineFlags.KEEP_CURRENT, + EngineFlags.KEEP_CURRENT + ); + + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.INVALID_CALLER)) + ); + } + } + + function test_updateSupplyCapBiggerMax() public { + (, uint256 daiSupplyCapBefore) = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveCaps( + AaveV3EthereumAssets.DAI_UNDERLYING + ); + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + (daiSupplyCapBefore * 2) + 1, + EngineFlags.KEEP_CURRENT + ); + + vm.startPrank(user); + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.UPDATE_ABOVE_MAX)) + ); + } + } + + function test_updateBorrowCapBiggerMax() public { + (uint256 daiBorrowCapBefore, ) = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveCaps( + AaveV3EthereumAssets.DAI_UNDERLYING + ); + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + EngineFlags.KEEP_CURRENT, + (daiBorrowCapBefore * 2) + 1 + ); + + vm.startPrank(user); + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.UPDATE_ABOVE_MAX)) + ); + } + } + + function test_updateSupplyCapNotStrictlyHigher() public { + (, uint256 daiSupplyCapBefore) = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveCaps( + AaveV3EthereumAssets.DAI_UNDERLYING + ); + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + daiSupplyCapBefore, + EngineFlags.KEEP_CURRENT + ); + + vm.startPrank(user); + // should fail when cap is equal current value + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.NOT_STRICTLY_HIGHER)) + ); + } + + // should also fail when lower + capUpdates[0].supplyCap -= 1; + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.NOT_STRICTLY_HIGHER)) + ); + } + } + + function test_updateBorrowCapNotStrictlyHigher() public { + (uint256 daiBorrowCapBefore, ) = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveCaps( + AaveV3EthereumAssets.DAI_UNDERLYING + ); + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + EngineFlags.KEEP_CURRENT, + daiBorrowCapBefore + ); + + vm.startPrank(user); + // should fail when cap is equal current value + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.NOT_STRICTLY_HIGHER)) + ); + } + + // should also fail when lower + capUpdates[0].borrowCap -= 1; + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.NOT_STRICTLY_HIGHER)) + ); + } + } + + function test_debounce() public { + (uint256 daiBorrowCapBefore, ) = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveCaps( + AaveV3EthereumAssets.DAI_UNDERLYING + ); + + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate( + AaveV3EthereumAssets.DAI_UNDERLYING, + EngineFlags.KEEP_CURRENT, + daiBorrowCapBefore + 1 + ); + + vm.startPrank(user); + steward.updateCaps(capUpdates); + + capUpdates[0].borrowCap += 1; + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == + keccak256(bytes(CapsPlusRiskStewardErrors.DEBOUNCE_NOT_RESPECTED)) + ); + } + + vm.warp(block.timestamp + steward.MINIMUM_DELAY() + 1); + steward.updateCaps(capUpdates); + } + + function test_unlisted() public { + address unlistedAsset = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; // stETH + + IAaveV3ConfigEngine.CapsUpdate[] memory capUpdates = new IAaveV3ConfigEngine.CapsUpdate[](1); + capUpdates[0] = IAaveV3ConfigEngine.CapsUpdate(unlistedAsset, 100, 100); + + vm.startPrank(user); + + try steward.updateCaps(capUpdates) { + require(false, 'MUST_FAIL'); + } catch Error(string memory reason) { + require( + keccak256(bytes(reason)) == keccak256(bytes(CapsPlusRiskStewardErrors.NO_CAP_INITIALIZE)) + ); + } + } +} diff --git a/yarn.lock b/yarn.lock index 9769abd1..9d68182f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,10 +60,10 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" -"@solidity-parser/parser@^0.14.3": - version "0.14.3" - resolved "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.3.tgz" - integrity sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw== +"@solidity-parser/parser@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.0.tgz#1fb418c816ca1fc3a1e94b08bcfe623ec4e1add4" + integrity sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q== dependencies: antlr4ts "^0.5.0-alpha.4" @@ -120,21 +120,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -data-uri-to-buffer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" - integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== - -diffler@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/diffler/-/diffler-2.0.4.tgz#1d00f4e773fa21a89a07db3140e65012d15e046b" - integrity sha512-RSKaYX48q/qdMT5pK7EurV4n1iBjTwe/92exZ9JeX+yXvDmRMKUHl1CAhLTEzrZ9HWj/utuDBaV/fuAzsDrtoA== - -emoji-regex@^10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.1.0.tgz" - integrity sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" @@ -145,26 +130,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" - integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== - dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" - integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== - dependencies: - fetch-blob "^3.1.2" - get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -175,14 +140,6 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -isomorphic-unfetch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-4.0.2.tgz#5fc04eeb1053b7b702278e2cf7a3f246cb3a9214" - integrity sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA== - dependencies: - node-fetch "^3.2.0" - unfetch "^5.0.0" - isomorphic-ws@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" @@ -202,11 +159,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -node-domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - node-fetch@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" @@ -214,46 +166,34 @@ node-fetch@^2.6.9: dependencies: whatwg-url "^5.0.0" -node-fetch@^3.2.0, node-fetch@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.1.tgz#b3eea7b54b3a48020e46f4f88b9c5a7430d20b2e" - integrity sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow== - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - object-hash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== -prettier-plugin-solidity@^1.0.0-beta.19: - version "1.0.0-dev.23" - resolved "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-dev.23.tgz" - integrity sha512-440/jZzvtDJcqtoRCQiigo1DYTPAZ85pjNg7gvdd+Lds6QYgID8RyOdygmudzHdFmV2UfENt//A8tzx7iS58GA== +prettier-plugin-solidity@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz#9a35124f578404caf617634a8cab80862d726cba" + integrity sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg== dependencies: - "@solidity-parser/parser" "^0.14.3" - emoji-regex "^10.1.0" - escape-string-regexp "^4.0.0" - semver "^7.3.7" + "@solidity-parser/parser" "^0.16.0" + semver "^7.3.8" solidity-comments-extractor "^0.0.7" - string-width "^4.2.3" -prettier@^2.7.1: - version "2.7.1" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== +prettier@^2.8.3: + version "2.8.7" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" + integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -semver@^7.3.7: - version "7.3.7" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== +semver@^7.3.8: + version "7.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" + integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== dependencies: lru-cache "^6.0.0" @@ -283,11 +223,6 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -unfetch@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-5.0.0.tgz#8a5b6e5779ebe4dde0049f7d7a81d4a1af99d142" - integrity sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg== - viem@^0.3.0: version "0.3.11" resolved "https://registry.yarnpkg.com/viem/-/viem-0.3.11.tgz#436b47d66f0b9190ed24c0164294df6b84825acb" @@ -303,11 +238,6 @@ viem@^0.3.0: isomorphic-ws "5.0.0" ws "8.12.0" -web-streams-polyfill@^3.0.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" - integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"