diff --git a/Makefile b/Makefile index cc5cf22..2ad40a2 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,8 @@ test-mimatic-fantom :; forge test -vvv --match-contract MIMATICAaveV3FantomListi test-harmony-freezing :; forge test -vvv --match-contract FreezeAllReservesAaveV3HarmonyByGuardian --rpc-url=${ETH_RPC_URL} --fork-block-number 29264480 test-fantom-freezing :; forge test -vvv --match-contract FreezeAllReservesAaveV3FantomByGuardian --rpc-url=${ETH_RPC_URL} --fork-block-number 46881340 test-btcb-ava:; forge test -vvv --match-contract BTCBAaveV3AvaListingByGuardian +test-risk-parameter-update:; forge test -vvv --match-contract RiskParameterUpdateByGuardian --rpc-url=${ETH_RPC_URL} --fork-block-number 22760128 +test-borrow-cap-update:; forge test -vvv --match-contract BorrowCapUpdateByGuardian --rpc-url=${ETH_RPC_URL} --fork-block-number 22760128 trace :; forge test -vvvv --rpc-url=${ETH_RPC_URL} --fork-block-number 16146270 clean :; forge clean snapshot :; forge snapshot @@ -31,3 +33,15 @@ deploy-fantom-freeze-steward :; forge script script/DeployFantomFreezeSteward.s verify-fantom-freeze-steward :; forge script script/DeployFantomFreezeSteward.s.sol:DeployFantomFreezeSteward --rpc-url ${ETH_RPC_URL} --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify --etherscan-api-key ${ETHERSCAN_API_KEY} -vvvv deploy-ava-btcb-steward :; forge script script/DeployAvaBTCbSteward.s.sol:DeployAvaBTCbSteward --rpc-url ${ETH_RPC_URL} --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify --etherscan-api-key ${ETHERSCAN_API_KEY} -vvvv verify-ava-btcb-steward :; forge script script/DeployAvaBTCbSteward.s.sol:DeployAvaBTCbSteward --rpc-url ${ETH_RPC_URL} --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify --etherscan-api-key ${ETHERSCAN_API_KEY} -vvvv + +deploy-ava-risk-update-steward:; forge script script/DeployAaveV3AvaRiskParameterSteward.s.sol:DeployAaveV3AvaRiskParameterSteward --rpc-url ${ETH_RPC_URL} --broadcast --legacy --private-key ${PRIVATE_KEY} --verify --etherscan-api-key ${ETHERSCAN_API_KEY} -vvvv +deploy-ava-risk-update-steward-dry-run:; forge script script/DeployAaveV3AvaRiskParameterSteward.s.sol:DeployAaveV3AvaRiskParameterSteward --rpc-url ${ETH_RPC_URL} --legacy -vvvv + +execute-ava-risk-update-steward:; forge script script/ExecuteAaveV3AvaRiskParameterSteward.s.sol:ExecuteAaveV3AvaRiskParameterSteward --rpc-url ${ETH_RPC_URL} --broadcast --legacy --private-key ${PRIVATE_KEY} -vvvv +execute-ava-risk-update-steward-dry-run:; forge script script/ExecuteAaveV3AvaRiskParameterSteward.s.sol:ExecuteAaveV3AvaRiskParameterSteward --rpc-url ${ETH_RPC_URL} --private-key ${PRIVATE_KEY} --legacy -vvvv + +deploy-ava-borrow-cap-steward:; forge script script/DeployAaveV3AvaBorrowCapsSteward.s.sol:DeployAaveV3AvaBorrowCapsSteward --rpc-url ${ETH_RPC_URL} --broadcast --legacy --private-key ${PRIVATE_KEY} --verify --etherscan-api-key ${ETHERSCAN_API_KEY} -vvvv +deploy-ava-borrow-cap-steward-dry-run:; forge script script/DeployAaveV3AvaBorrowCapsSteward.s.sol:DeployAaveV3AvaBorrowCapsSteward --rpc-url ${ETH_RPC_URL} --legacy -vvvv + +execute-ava-borrow-cap-steward:; forge script script/ExecuteAaveV3AvaBorrowCapsSteward.s.sol:ExecuteAaveV3AvaBorrowCapsSteward --rpc-url ${ETH_RPC_URL} --broadcast --legacy --private-key ${PRIVATE_KEY} -vvvv +execute-ava-borrow-cap-steward-dry-run:; forge script script/ExecuteAaveV3AvaBorrowCapsSteward.s.sol:ExecuteAaveV3AvaBorrowCapsSteward --rpc-url ${ETH_RPC_URL} --private-key ${PRIVATE_KEY} --legacy -vvvv diff --git a/README.md b/README.md index 940c03c..d2b2a8a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Helper smart contracts to list assets on Aave v3 or change configs. Designed to | Multiple | config-change | Aave v3 Harmony | [Harmony freezing](./src/contracts/harmony-protection/FreezeHarmonyPoolReservesSteward.sol) | [Tests](./src/test/FreezeAllReservesAaveV3FantomByGuardian.t.sol) | [0xf202866d9fb6f089587d86d4128e7c8e0fdf94fe](https://explorer.harmony.one/address/0xf202866d9fb6f089587d86d4128e7c8e0fdf94fe) | | Multiple | config-change | Aave v3 Fantom | [Fantom freezing](./src/contracts/fantom-freeze/FreezeFantomPoolReservesSteward.sol) | [Tests](./src/test/FreezeAllReservesAaveV3HarmonyByGuardian.t.sol) | [0x1aa435ed226014407fa6b889e9d06c02b1a12af3](https://ftmscan.com/address/0x1aa435ed226014407fa6b889e9d06c02b1a12af3#code) | | BTC.b | asset-listing | Aave v3 Avalanche | [BTC.b Steward](./src/contracts/btc.b/AaveV3AvaBTCBListingSteward.sol) | [Tests](./src/test/BTCBAaveV3AvaListingByGuardian.t.sol) | [0xeee4877a56392c82578df71e8b9270ad8cbabfdc](https://snowtrace.io/address/0xeee4877a56392c82578df71e8b9270ad8cbabfdc#code) | +| Multiple | config-change | Aave v3 Ava | [Risk Parameter Updates](./src/contracts/gauntlet/AaveV3AvaRiskParameterUpdate.sol) | [Tests](./src/test/RiskParameterUpdateByGuardian.t.sol) | Multiple | ### Copyright diff --git a/script/DeployAaveV3AvaBorrowCapsSteward.s.sol b/script/DeployAaveV3AvaBorrowCapsSteward.s.sol new file mode 100644 index 0000000..d34ecda --- /dev/null +++ b/script/DeployAaveV3AvaBorrowCapsSteward.s.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/console.sol'; +import {Script} from 'forge-std/Script.sol'; +import {AaveV3AvaBorrowCapsUpdate} from '../src/contracts/gauntlet/AaveV3AvaBorrowCapsUpdate.sol'; + +contract DeployAaveV3AvaBorrowCapsSteward is Script { + function run() external { + vm.startBroadcast(); + new AaveV3AvaBorrowCapsUpdate(); + vm.stopBroadcast(); + } +} diff --git a/script/DeployAaveV3AvaRiskParameterSteward.s.sol b/script/DeployAaveV3AvaRiskParameterSteward.s.sol new file mode 100644 index 0000000..b60b61c --- /dev/null +++ b/script/DeployAaveV3AvaRiskParameterSteward.s.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/console.sol'; +import {Script} from 'forge-std/Script.sol'; +import {AaveV3AvaRiskParameterUpdate} from '../src/contracts/gauntlet/AaveV3AvaRiskParameterUpdate.sol'; + +contract DeployAaveV3AvaRiskParameterSteward is Script { + function run() external { + vm.startBroadcast(); + new AaveV3AvaRiskParameterUpdate(); + vm.stopBroadcast(); + } +} diff --git a/script/ExecuteAaveV3AvaBorrowCapsSteward.s.sol b/script/ExecuteAaveV3AvaBorrowCapsSteward.s.sol new file mode 100644 index 0000000..93c76f6 --- /dev/null +++ b/script/ExecuteAaveV3AvaBorrowCapsSteward.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/console.sol'; +import {Script} from 'forge-std/Script.sol'; +import {AaveV3AvaBorrowCapsUpdate} from '../src/contracts/gauntlet/AaveV3AvaBorrowCapsUpdate.sol'; + +address constant BORROW_CAPS_STEWARD = 0x4393277B02ef3cA293990A772B7160a8c76F2443; + +contract ExecuteAaveV3AvaBorrowCapsSteward is Script { + function run() external { + AaveV3AvaBorrowCapsUpdate steward = AaveV3AvaBorrowCapsUpdate(BORROW_CAPS_STEWARD); + vm.startBroadcast(); + steward.execute(); + vm.stopBroadcast(); + } +} diff --git a/script/ExecuteAaveV3AvaRiskParameterSteward.s.sol b/script/ExecuteAaveV3AvaRiskParameterSteward.s.sol new file mode 100644 index 0000000..786b508 --- /dev/null +++ b/script/ExecuteAaveV3AvaRiskParameterSteward.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/console.sol'; +import {Script} from 'forge-std/Script.sol'; +import {AaveV3AvaRiskParameterUpdate} from '../src/contracts/gauntlet/AaveV3AvaRiskParameterUpdate.sol'; + +address constant RISK_STEWARD = 0x4c68fDA91bfb4683eAB90017d9B76a99F2d77Eed; + +contract ExecuteAaveV3AvaRiskParameterSteward is Script { + function run() external { + AaveV3AvaRiskParameterUpdate steward = AaveV3AvaRiskParameterUpdate(RISK_STEWARD); + vm.startBroadcast(); + steward.execute(); + vm.stopBroadcast(); + } +} diff --git a/src/contracts/gauntlet/AaveV3AvaBorrowCapsUpdate.sol b/src/contracts/gauntlet/AaveV3AvaBorrowCapsUpdate.sol new file mode 100644 index 0000000..1711bd1 --- /dev/null +++ b/src/contracts/gauntlet/AaveV3AvaBorrowCapsUpdate.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import '../common/StewardBase.sol'; +import {AaveV3Avalanche} from 'aave-address-book/AaveV3Avalanche.sol'; + +uint256 constant NUM_UPDATES = 3; + +struct ParameterSet { + string symbol; + address _address; + uint256 borrowCap; +} + +/** + * @dev This steward sets borrow caps for collateral assets on Aave V3 Avalanche + * - Snapshot: https://snapshot.org/#/aave.eth/proposal/0x3f4a96dcf93b2d9c7cbfa6c1b627f995ed420e57492b333843783434588f4370 + * - Dicussion: https://governance.aave.com/t/arc-risk-parameter-updates-for-aave-v2-polygon-and-aave-v3-avax-2022-11-23/10793 + */ +contract AaveV3AvaBorrowCapsUpdate is StewardBase { + function _getUpdates() external pure returns ( + ParameterSet[NUM_UPDATES] memory + ) { + ParameterSet[NUM_UPDATES] memory parameters = [ + ParameterSet({ + symbol: 'FRAX', + _address: 0xD24C2Ad096400B6FBcd2ad8B24E7acBc21A1da64, + borrowCap: 2000000 + }), + ParameterSet({ + symbol: 'MAI', + _address: 0x5c49b268c9841AFF1Cc3B0a418ff5c3442eE3F3b, + borrowCap: 2000000 + }), + ParameterSet({ + symbol: 'LINK.e', + _address: 0x5947BB275c521040051D82396192181b413227A3, + borrowCap: 220000 + }) + ]; + + return parameters; + } + + function execute() + external + withRennounceOfAllAavePermissions(AaveV3Avalanche.ACL_MANAGER) + withOwnershipBurning + onlyOwner + { + IPoolConfigurator configurator = AaveV3Avalanche.POOL_CONFIGURATOR; + + ParameterSet[NUM_UPDATES] memory parameters = this._getUpdates(); + + for (uint256 i = 0; i < parameters.length; i++) { + ParameterSet memory parameterSet = parameters[i]; + configurator.setBorrowCap( + parameterSet._address, + parameterSet.borrowCap + ); + } + } +} diff --git a/src/contracts/gauntlet/AaveV3AvaRiskParameterUpdate.sol b/src/contracts/gauntlet/AaveV3AvaRiskParameterUpdate.sol new file mode 100644 index 0000000..e55c0de --- /dev/null +++ b/src/contracts/gauntlet/AaveV3AvaRiskParameterUpdate.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import '../common/StewardBase.sol'; +import {AaveV3Avalanche} from 'aave-address-book/AaveV3Avalanche.sol'; + +uint256 constant NUM_UPDATES = 5; + +struct ParameterSet { + string symbol; + address _address; + uint32 ltv; + uint32 liquidationThreshold; + uint32 liquidationBonus; +} + +/** + * @dev This steward sets risk parameters for collateral assets on Aave V3 Avalanche + * - Snapshot: https://snapshot.org/#/aave.eth/proposal/0xa95e81de4734e676409ec16f5ea8206279e8eb2fab3f4fb3fca779f54d78f7fd + * - Dicussion: https://governance.aave.com/t/arc-risk-parameter-updates-for-aave-v3-avalanche-2022-10-15/10280/ + */ +contract AaveV3AvaRiskParameterUpdate is StewardBase { + function _getUpdates() external pure returns ( + ParameterSet[NUM_UPDATES] memory + ) { + ParameterSet[NUM_UPDATES] memory parameters = [ + ParameterSet({ + symbol: 'AAVE.e', + _address: 0x63a72806098Bd3D9520cC43356dD78afe5D386D9, + ltv: 6000, + liquidationThreshold: 7130, + liquidationBonus: 10750 + }), + ParameterSet({ + symbol: 'DAI.e', + _address: 0xd586E7F844cEa2F87f50152665BCbc2C279D8d70, + ltv: 7500, + liquidationThreshold: 8200, + liquidationBonus: 10500 + }), + ParameterSet({ + symbol: 'USDC', + _address: 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E, + ltv: 8250, + liquidationThreshold: 8625, + liquidationBonus: 10400 + }), + ParameterSet({ + symbol: 'USDt', + _address: 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7, + ltv: 7500, + liquidationThreshold: 8100, + liquidationBonus: 10500 + }), + ParameterSet({ + symbol: 'WBTC.e', + _address: 0x50b7545627a5162F82A992c33b87aDc75187B218, + ltv: 7000, + liquidationThreshold: 7500, + liquidationBonus: 10625 + }) + ]; + + return parameters; + } + + function execute() + external + withRennounceOfAllAavePermissions(AaveV3Avalanche.ACL_MANAGER) + withOwnershipBurning + onlyOwner + { + IPoolConfigurator configurator = AaveV3Avalanche.POOL_CONFIGURATOR; + + ParameterSet[NUM_UPDATES] memory parameters = this._getUpdates(); + + for (uint256 i = 0; i < parameters.length; i++) { + ParameterSet memory parameterSet = parameters[i]; + + configurator.configureReserveAsCollateral( + parameterSet._address, + parameterSet.ltv, + parameterSet.liquidationThreshold, + parameterSet.liquidationBonus + ); + } + } +} diff --git a/src/test/BorrowCapUpdateByGuardian.t.sol b/src/test/BorrowCapUpdateByGuardian.t.sol new file mode 100644 index 0000000..2fdebfd --- /dev/null +++ b/src/test/BorrowCapUpdateByGuardian.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import 'forge-std/Test.sol'; + +import {IPoolConfigurator, ConfiguratorInputTypes, IACLManager} from 'aave-address-book/AaveV3.sol'; +import {AaveV3Avalanche} from 'aave-address-book/AaveAddressBook.sol'; +import {AaveV3AvaBorrowCapsUpdate, ParameterSet, NUM_UPDATES} from '../contracts/gauntlet/AaveV3AvaBorrowCapsUpdate.sol'; +import {AaveV3Helpers, ReserveConfig, ReserveTokens, IERC20} from './helpers/AaveV3Helpers.sol'; + +contract BorrowCapUpdateByGuardian is Test { + using stdStorage for StdStorage; + + address public constant GUARDIAN_AVALANCHE = + 0xa35b76E4935449E33C56aB24b23fcd3246f13470; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl("avalanche"), 22760128); + } + + function testBorrowCapsUpdate() public { + ReserveConfig[] memory allConfigsBefore = AaveV3Helpers + ._getReservesConfigs(false); + + vm.startPrank(GUARDIAN_AVALANCHE); + + AaveV3AvaBorrowCapsUpdate updateSteward = new AaveV3AvaBorrowCapsUpdate(); + + IACLManager aclManager = AaveV3Avalanche.ACL_MANAGER; + + aclManager.addRiskAdmin(address(updateSteward)); + + updateSteward.execute(); + + vm.stopPrank(); + + ReserveConfig[] memory allConfigsAfter = AaveV3Helpers + ._getReservesConfigs(false); + + ParameterSet[NUM_UPDATES] memory parameters = updateSteward._getUpdates(); + string[] memory symbols = new string[](parameters.length); + + for (uint256 i = 0; i < parameters.length; i++) { + symbols[i] = parameters[i].symbol; + + ReserveConfig memory expectedConfig = AaveV3Helpers._findReserveConfig(allConfigsBefore, symbols[i], false); + expectedConfig.borrowCap = parameters[i].borrowCap; + + AaveV3Helpers._validateReserveConfig( + expectedConfig, + allConfigsAfter + ); + } + + AaveV3Helpers._noReservesConfigsChangesApartFromMany( + allConfigsBefore, + allConfigsAfter, + symbols + ); + + require( + updateSteward.owner() == address(0), + 'INVALID_OWNER_POST_LISTING' + ); + } +} diff --git a/src/test/RiskParameterUpdateByGuardian.t.sol b/src/test/RiskParameterUpdateByGuardian.t.sol new file mode 100644 index 0000000..4500622 --- /dev/null +++ b/src/test/RiskParameterUpdateByGuardian.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import 'forge-std/Test.sol'; + +import {IPoolConfigurator, ConfiguratorInputTypes, IACLManager} from 'aave-address-book/AaveV3.sol'; +import {AaveV3Avalanche} from 'aave-address-book/AaveAddressBook.sol'; +import {AaveV3AvaRiskParameterUpdate, ParameterSet, NUM_UPDATES} from '../contracts/gauntlet/AaveV3AvaRiskParameterUpdate.sol'; +import {AaveV3Helpers, ReserveConfig, ReserveTokens, IERC20} from './helpers/AaveV3Helpers.sol'; + +contract RiskParameterUpdateByGuardian is Test { + using stdStorage for StdStorage; + + address public constant GUARDIAN_AVALANCHE = + 0xa35b76E4935449E33C56aB24b23fcd3246f13470; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl("avalanche"), 22760128); + } + + function testRiskParameterUpdate() public { + ReserveConfig[] memory allConfigsBefore = AaveV3Helpers + ._getReservesConfigs(false); + + vm.startPrank(GUARDIAN_AVALANCHE); + + AaveV3AvaRiskParameterUpdate updateSteward = new AaveV3AvaRiskParameterUpdate(); + + IACLManager aclManager = AaveV3Avalanche.ACL_MANAGER; + + aclManager.addRiskAdmin(address(updateSteward)); + + updateSteward.execute(); + + vm.stopPrank(); + + ReserveConfig[] memory allConfigsAfter = AaveV3Helpers + ._getReservesConfigs(false); + + ParameterSet[NUM_UPDATES] memory parameters = updateSteward._getUpdates(); + string[] memory symbols = new string[](parameters.length); + + for (uint256 i = 0; i < parameters.length; i++) { + symbols[i] = parameters[i].symbol; + + ReserveConfig memory expectedConfig = AaveV3Helpers._findReserveConfig(allConfigsBefore, symbols[i], false); + expectedConfig.ltv = parameters[i].ltv; + expectedConfig.liquidationThreshold = parameters[i].liquidationThreshold; + expectedConfig.liquidationBonus = parameters[i].liquidationBonus; + + AaveV3Helpers._validateReserveConfig( + expectedConfig, + allConfigsAfter + ); + } + + AaveV3Helpers._noReservesConfigsChangesApartFromMany( + allConfigsBefore, + allConfigsAfter, + symbols + ); + + require( + updateSteward.owner() == address(0), + 'INVALID_OWNER_POST_LISTING' + ); + } +} diff --git a/src/test/helpers/AaveV3Helpers.sol b/src/test/helpers/AaveV3Helpers.sol index 326e69d..4677a41 100644 --- a/src/test/helpers/AaveV3Helpers.sol +++ b/src/test/helpers/AaveV3Helpers.sol @@ -699,6 +699,35 @@ library AaveV3Helpers { } } + function _noReservesConfigsChangesApartFromMany( + ReserveConfig[] memory allConfigsBefore, + ReserveConfig[] memory allConfigsAfter, + string[] memory assetChangedSymbols + ) internal pure { + require( + allConfigsBefore.length == allConfigsAfter.length, + 'A_UNEXPECTED_NEW_LISTING_HAPPENED' + ); + + for (uint256 i = 0; i < allConfigsBefore.length; i++) { + bool skip = false; + for (uint256 j = 0; j < assetChangedSymbols.length; j++) { + if ( + keccak256(abi.encodePacked(assetChangedSymbols[j])) == + keccak256(abi.encodePacked(allConfigsBefore[i].symbol)) + ) { + skip = true; + } + } + if (!skip) { + _requireNoChangeInConfigs( + allConfigsBefore[i], + allConfigsAfter[i] + ); + } + } + } + function _requireNoChangeInConfigs( ReserveConfig memory config1, ReserveConfig memory config2