diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ba9e366 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{Makefile,**.mk}] +# Use tabs for indentation (Makefiles require tabs) +indent_style = tab diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c12ce8f --- /dev/null +++ b/.env.example @@ -0,0 +1,34 @@ +# Deployment via ledger +MNEMONIC_INDEX= +LEDGER_SENDER= + +# Deployment via private key +PRIVATE_KEY= + +# Test rpc_endpoints +RPC_MAINNET=https://eth.llamarpc.com +RPC_AVALANCHE=https://api.avax.network/ext/bc/C/rpc +RPC_OPTIMISM=https://optimism.llamarpc.com +RPC_POLYGON=https://polygon.llamarpc.com +RPC_ARBITRUM=https://arbitrum.llamarpc.com +RPC_FANTOM=https://rpc.ftm.tools +RPC_HARMONY=https://api.harmony.one +RPC_METIS=https://andromeda.metis.io/?owner=1088 +RPC_BASE=https://base.llamarpc.com +RPC_ZKEVM=https://zkevm-rpc.com +RPC_GNOSIS=https://rpc.ankr.com/gnosis +RPC_BNB=https://binance.llamarpc.com + +# Etherscan api keys for verification & download utils +ETHERSCAN_API_KEY_MAINNET= +ETHERSCAN_API_KEY_POLYGON= +ETHERSCAN_API_KEY_AVALANCHE= +ETHERSCAN_API_KEY_FANTOM= +ETHERSCAN_API_KEY_OPTIMISM= +ETHERSCAN_API_KEY_ARBITRUM= +ETHERSCAN_API_KEY_BASE= +ETHERSCAN_API_KEY_ZKEVM= +ETHERSCAN_API_KEY_GNOSIS= +ETHERSCAN_API_KEY_BNB= + + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b75a24e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,13 @@ +name: Main workflow + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +jobs: + test: + uses: bgd-labs/github-workflows/.github/workflows/foundry-test.yml@main + secrets: inherit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d95519b --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# build and cache +cache/ +out/ + +# general +.env + +# editors +.idea +.vscode + +# well, looks strange to ignore package-lock, but we have only pretter and it's temproray +package-lock.json +node_modules + +# ignore foundry deploy artifacts +broadcast/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..227371f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "lib/aave-address-book"] + path = lib/aave-address-book + url = https://github.com/bgd-labs/aave-address-book +[submodule "lib/cl-synchronicity-price-adapter"] + path = lib/cl-synchronicity-price-adapter + url = https://github.com/bgd-labs/cl-synchronicity-price-adapter +[submodule "lib/aave-helpers"] + path = lib/aave-helpers + url = https://github.com/bgd-labs/aave-helpers diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3390abe --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +out +lib +cache +node_modules diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..ad97557 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,24 @@ +{ + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "bracketSpacing": false + } + }, + { + "files": "*.ts", + "options": { + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "bracketSpacing": false + } + } + ] +} diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..05a2e27 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,10 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["error", "^0.8.0"], + "func-visibility": ["warn", { "ignoreConstructors": true }], + "compiler-fixed": false, + "quotes": ["error", "single"], + "indent": [2] + } +} diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.solhintignore @@ -0,0 +1 @@ +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee0673c --- /dev/null +++ b/LICENSE @@ -0,0 +1,119 @@ +Business Source License 1.1 + +License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved. +“Business Source License” is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +Licensor: Aave DAO, represented by its governance smart contracts + +Licensed Work: Price cap adapters + The Licensed Work is (c) 2024 Aave DAO, represented by its governance smart contracts + +Additional Use Grant: You are permitted to use, copy, and modify the Licensed Work, subject to + the following conditions: + - Your use of the Licensed Work shall not, directly or indirectly, enable, facilitate, + or assist in any way with the migration of users and/or funds from the Aave ecosystem. + The "Aave ecosystem" is defined in the context of this License as the collection of + software protocols and applications approved by the Aave governance, including all + those produced within compensated service provider engagements with the Aave DAO. + The Aave DAO is able to waive this requirement for one or more third-parties, if and + only if explicitly indicating it on a record 'authorizations' on adi.aavelicense.eth, + controlled by the Aave DAO. + - You are neither an individual nor a direct or indirect participant in any incorporated + organization, DAO, or identifiable group, that has deployed in production any original + or derived software ("fork") of the Aave ecosystem for purposes competitive to Aave, + within the preceding two years. + The Aave DAO is able to waive this requirement for one or more third-parties, if and + only if explicitly indicating it on a record 'authorizations' on adi.aavelicense.eth, + controlled by the Aave DAO. + - You must ensure that the usage of the Licensed Work does not result in any direct or + indirect harm to the Aave ecosystem or the Aave brand. This encompasses, but is not limited to, + reputational damage, omission of proper credit/attribution, or utilization for any malicious + intent. + +Change Date: The earlier of: + - 2027-07-27 + - The date specified in the 'change-date' record on adi.aavelicense.eth, controlled by + the Aave DAO + +Change License: MIT + +----------------------------------------------------------------------------- + +Notice + +The Business Source License (this document, or the “License”) is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. + +----------------------------------------------------------------------------- + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark “Business Source License”, +as long as you comply with the Covenants of Licensor below. + +Covenants of Licensor + +In consideration of the right to use this License’s text and the “Business +Source License” name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where “compatible” means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text “None”. + +3. To specify a Change Date. + +4. Not to modify this License in any other way. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a0966ff --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +# include .env file and export its env vars +# (-include to ignore error if it does not exist) +-include .env + +# deps +update:; forge update + +# Build & test +build :; forge build --sizes +test :; forge test -vvv + +# Utilities +download :; cast etherscan-source --chain ${chain} -d src/etherscan/${chain}_${address} ${address} +git-diff : + @mkdir -p diffs + @npx prettier ${before} ${after} --write + @printf '%s\n%s\n%s\n' "\`\`\`diff" "$$(git diff --no-index --diff-algorithm=patience --ignore-space-at-eol ${before} ${after})" "\`\`\`" > diffs/${out}.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..aabc113 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Correlated-assets price oracle + +Price oracle adapter smart contracts, introducing different types of range price protection on oracle feeds used by the Aave protocol. + +
+ +## Types + +### [RatioCapPriceAdapter](./src/contracts/PriceCapAdapterBase.sol) + +Certain assets like LSTs (Liquid Staking Tokens) are highly correlated to an underlying, with an additional growth component on top of it, and sometimes, slashing dynamics. This initial version of an adapter for this use case adds an upper side protection. + +High-level, the idea is doing periodic updates on 3 parameters: a snapshot ratio, its timestamp, and a max allowed ratio growth in yearly percentage. +Every time the price adapter is queried, it will get the current ratio of the asset/underlying and compared it with a dynamically calculated upper value of that ratio, using the previously defined parameters. +If the current ratio is above the ratio cap, the ratio cap is returned. If not, the current ratio is. + +
+ +**Misc considerations** + +- Maximum precision is not the objective of this implementation, as anyway, the cap is thought to have a good margin, given that the risk parameters of the Aave protocol should protect enough. +- Some basic safety checks are applied when setting parameters, but as this is access controlled to a trusted entity (e.g. Aave governance), they are not designed to be exhaustive. The idea is to build additional update layers on top of this system (e.g. Risk Stewards) to cover extra limitations. +- Timestamp of snapshots should not decrease from one parameters update to the next. +- To optimise calculations, the maximum yearly ratio growth received in yearly bps (e.g. 5_00 for 5%) is converted to ratio growth per second internally. We expose in yearly bps percentage to follow similar approach as on other Aave systems (BGD config engine), and because we believe it is more intuitive for integrations. +- Given that each asset on which to apply this adapter has its own characteristics, the base contract is `abstract` to allow the child to define its own specificities (mainly the way of calculating/fetching the current ratio). +- On construction, an extra `MINIMUM_SNAPSHOT_DELAY` is configured, indicating the minimum time (in seconds) to have passed since the snapshot ratio timestamp, and the current moment (block.timestamp). This is required because frequently, the update of the ration in a correlated asset is a "discrete" event, which causes a spike of value by time until enough time passes. E.g. if the snapshot ratio is set 1 second before the ratio was updated in an LST, 2 seconds after the increase by unit of time would be really high. + + Defining a proper value depends on specific characteristics of the correlated asset, but generally, a conservative way would be setting a long enough delay, like 7 days. + +
+ +### [FixCapPriceAdapter](./src/contracts/PriceCapAdapterStable.sol) + +In some cases, the relation between an underlying asset and its correlated is direct, without any type of continuous growth expected. For example, this is the case of USD-pegged stable coins, where USD is the underlying and let's say USDC is the correlated asset. + +Initially we thought to model this as a sub-case of `RatioCapPriceAdapter`, with 0 ratio growth, but finally we decided to create a simplified version of the adapter, removing completely the growth component. + +
+ +
+ +## License + +Copyright © 2024, Aave DAO, represented by its governance smart contracts. + +Created by [BGD Labs](https://bgdlabs.com/). + +The default license of this repository is [BUSL1.1](./LICENSE), but all interfaces and the content of the [libs folder](./src/contracts/libs/) and [Polygon tunnel](./src/contracts/adapters/polygon/tunnel/) folders are open source, MIT-licensed. + +**IMPORTANT**. The BUSL1.1 license of this repository allows for any usage of the software, if respecting the *Additional Use Grant* limitations, forbidding any use case damaging anyhow the Aave DAO's interests. diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..517bda4 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,5 @@ +# To load the variables in the .env file +source .env + +# To deploy and verify our contract +forge script script/Ghost.s.sol:Deploy --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vvvv diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..1292908 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,38 @@ +[profile.default] +src = 'src' +test = 'tests' +script = 'scripts' +out = 'out' +libs = ['lib'] +remappings = [ +] +fs_permissions = [{ access = "write", path = "./reports" }] + +[rpc_endpoints] +mainnet = "${RPC_MAINNET}" +optimism = "${RPC_OPTIMISM}" +avalanche = "${RPC_AVALANCHE}" +polygon = "${RPC_POLYGON}" +arbitrum = "${RPC_ARBITRUM}" +fantom = "${RPC_FANTOM}" +harmony = "${RPC_HARMONY}" +metis = "${RPC_METIS}" +base = "${RPC_BASE}" +zkevm = "${RPC_ZKEVM}" +gnosis = "${RPC_GNOSIS}" +bnb = "${RPC_BNB}" + +[etherscan] +mainnet = { key="${ETHERSCAN_API_KEY_MAINNET}", chainId=1 } +optimism = { key="${ETHERSCAN_API_KEY_OPTIMISM}", chainId=10 } +avalanche = { key="${ETHERSCAN_API_KEY_AVALANCHE}", chainId=43114 } +polygon = { key="${ETHERSCAN_API_KEY_POLYGON}", chainId=137 } +arbitrum = { key="${ETHERSCAN_API_KEY_ARBITRUM}", chainId=42161 } +fantom = { key="${ETHERSCAN_API_KEY_FANTOM}", chainId=250 } +metis = { key="any", chainId=1088, url='https://andromeda-explorer.metis.io/' } +base = { key="${ETHERSCAN_API_KEY_BASE}", chainId=8453 } +zkevm = { key="${ETHERSCAN_API_KEY_ZKEVM}", chainId=1101 } +gnosis = { key="${ETHERSCAN_API_KEY_GNOSIS}", chainId=100 } +bnb = { key="${ETHERSCAN_API_KEY_BNB}", chainId=56 } + +# See more config options https://github.com/gakonst/foundry/tree/master/config diff --git a/lib/aave-address-book b/lib/aave-address-book new file mode 160000 index 0000000..555924c --- /dev/null +++ b/lib/aave-address-book @@ -0,0 +1 @@ +Subproject commit 555924cf8a341071668af0dd20374cd3dbd66655 diff --git a/lib/aave-helpers b/lib/aave-helpers new file mode 160000 index 0000000..3b4c2ab --- /dev/null +++ b/lib/aave-helpers @@ -0,0 +1 @@ +Subproject commit 3b4c2abdef6efb1a984e0e76227c055caca2aa5b diff --git a/lib/cl-synchronicity-price-adapter b/lib/cl-synchronicity-price-adapter new file mode 160000 index 0000000..0e800cc --- /dev/null +++ b/lib/cl-synchronicity-price-adapter @@ -0,0 +1 @@ +Subproject commit 0e800cc8596449cd541310ca5bee07d1117f2257 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..4513bc2 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 4513bc2063f23c57bee6558799584b518d387a39 diff --git a/package.json b/package.json new file mode 100644 index 0000000..627b161 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "bgd-forge-template", + "version": "1.0.0", + "scripts": { + "lint": "prettier ./", + "lint:fix": "npm run lint -- --write" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/bgd-labs/bgd-forge-template.git" + }, + "keywords": [], + "author": "BGD labs", + "license": "MIT", + "bugs": { + "url": "https://github.com/bgd-labs/bgd-forge-template/issues" + }, + "homepage": "https://github.com/bgd-labs/bgd-forge-template#readme", + "devDependencies": { + "prettier": "2.8.7", + "prettier-plugin-solidity": "1.1.3" + } +} diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..ce88588 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,6 @@ +forge-std/=lib/forge-std/src/ +aave-address-book/=lib/aave-address-book/src/ +cl-synchronicity-price-adapter/=lib/cl-synchronicity-price-adapter/src/ +aave-v3-core/=lib/aave-address-book/lib/aave-v3-core/ +aave-helpers/=lib/aave-helpers/src/ +solidity-utils/=lib/aave-helpers/lib/solidity-utils/src/ diff --git a/reports/.empty b/reports/.empty new file mode 100644 index 0000000..e69de29 diff --git a/scripts/DeployArbitrum.s.sol b/scripts/DeployArbitrum.s.sol new file mode 100644 index 0000000..ff7fe2b --- /dev/null +++ b/scripts/DeployArbitrum.s.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {ArbitrumScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Arbitrum, AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {BaseAggregatorsArbitrum} from 'cl-synchronicity-price-adapter/lib/BaseAggregatorsArbitrum.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {CLRatePriceCapAdapter, IPriceCapAdapter} from '../src/contracts/CLRatePriceCapAdapter.sol'; +import {AaveV3ArbitrumPayload} from '../src/contracts/payloads/AaveV3ArbitrumPayload.sol'; + +library CapAdaptersCodeArbitrum { + bytes public constant USDT_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Arbitrum.ACL_MANAGER, + AaveV3ArbitrumAssets.USDT_ORACLE, + 'Capped USDT/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Arbitrum.ACL_MANAGER, + AaveV3ArbitrumAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant DAI_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Arbitrum.ACL_MANAGER, + AaveV3ArbitrumAssets.DAI_ORACLE, + 'Capped DAI/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant LUSD_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Arbitrum.ACL_MANAGER, + AaveV3ArbitrumAssets.LUSD_ORACLE, + 'Capped LUSD/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant FRAX_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Arbitrum.ACL_MANAGER, + AaveV3ArbitrumAssets.FRAX_ORACLE, + 'Capped FRAX/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant rETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Arbitrum.ACL_MANAGER, + AaveV3ArbitrumAssets.WETH_ORACLE, + BaseAggregatorsArbitrum.RETH_ETH_AGGREGATOR, + 'Capped rETH / ETH / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant wstETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Arbitrum.ACL_MANAGER, + AaveV3ArbitrumAssets.WETH_ORACLE, + BaseAggregatorsArbitrum.WSTETH_STETH_AGGREGATOR, + 'Capped wstETH / stETH(ETH) / USD', // TODO: is it actually going to STETH, but then using ETH feed + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); +} + +contract DeployArbitrum is ArbitrumScript { + function run() external broadcast { + AaveV3ArbitrumPayload.Adapters memory adapters; + + adapters.usdtAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeArbitrum.USDT_ADAPTER_CODE + ); + adapters.usdcAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeArbitrum.USDC_ADAPTER_CODE + ); + adapters.daiAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeArbitrum.DAI_ADAPTER_CODE + ); + adapters.lusdAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeArbitrum.LUSD_ADAPTER_CODE + ); + adapters.fraxAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeArbitrum.FRAX_ADAPTER_CODE + ); + adapters.rEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeArbitrum.rETH_ADAPTER_CODE + ); + adapters.wstEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeArbitrum.wstETH_ADAPTER_CODE + ); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3ArbitrumPayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployAvalanche.s.sol b/scripts/DeployAvalanche.s.sol new file mode 100644 index 0000000..3ed1800 --- /dev/null +++ b/scripts/DeployAvalanche.s.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {AvalancheScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Avalanche, AaveV3AvalancheAssets} from 'aave-address-book/AaveV3Avalanche.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {SAvaxPriceCapAdapter, IPriceCapAdapter} from '../src/contracts/SAvaxPriceCapAdapter.sol'; +import {AaveV3AvalanchePayload} from '../src/contracts/payloads/AaveV3AvalanchePayload.sol'; + +library CapAdaptersCodeAvalanche { + bytes public constant USDt_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Avalanche.ACL_MANAGER, + AaveV3AvalancheAssets.USDt_ORACLE, + 'Capped USDt/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Avalanche.ACL_MANAGER, + AaveV3AvalancheAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant DAIe_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Avalanche.ACL_MANAGER, + AaveV3AvalancheAssets.DAIe_ORACLE, + 'Capped DAI.e/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant FRAX_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Avalanche.ACL_MANAGER, + AaveV3AvalancheAssets.FRAX_ORACLE, + 'Capped FRAX/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + + bytes public constant sAVAX_ADAPTER_CODE = + abi.encodePacked( + type(SAvaxPriceCapAdapter).creationCode, + abi.encode( + AaveV3Avalanche.ACL_MANAGER, + AaveV3AvalancheAssets.WAVAX_ORACLE, + AaveV3AvalancheAssets.sAVAX_UNDERLYING, + 'Capped sAVAX / AVAX / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); +} + +contract DeployAvalanche is AvalancheScript { + function run() external broadcast { + AaveV3AvalanchePayload.Adapters memory adapters; + + adapters.usdtAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeAvalanche.USDt_ADAPTER_CODE + ); + adapters.usdcAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeAvalanche.USDC_ADAPTER_CODE + ); + adapters.daieAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeAvalanche.DAIe_ADAPTER_CODE + ); + adapters.fraxAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeAvalanche.FRAX_ADAPTER_CODE + ); + adapters.sAvaxAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeAvalanche.sAVAX_ADAPTER_CODE + ); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3AvalanchePayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployBase.s.sol b/scripts/DeployBase.s.sol new file mode 100644 index 0000000..2eef706 --- /dev/null +++ b/scripts/DeployBase.s.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {BaseScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Base, AaveV3BaseAssets} from 'aave-address-book/AaveV3Base.sol'; +import {BaseAggregatorsBase} from 'cl-synchronicity-price-adapter/lib/BaseAggregatorsBase.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {CLRatePriceCapAdapter, IPriceCapAdapter} from '../src/contracts/CLRatePriceCapAdapter.sol'; +import {AaveV3BasePayload} from '../src/contracts/payloads/AaveV3BasePayload.sol'; + +library CapAdaptersCodeBase { + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Base.ACL_MANAGER, + AaveV3BaseAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + + bytes public constant wstETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Base.ACL_MANAGER, + AaveV3BaseAssets.WETH_ORACLE, + BaseAggregatorsBase.WSTETH_STETH_AGGREGATOR, + 'Capped wstETH / stETH(ETH) / USD', // TODO: is it actually going to STETH, but then using ETH feed + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant cbETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Base.ACL_MANAGER, + AaveV3BaseAssets.WETH_ORACLE, + BaseAggregatorsBase.CBETH_ETH_AGGREGATOR, + 'Capped cbETH / ETH / USD', // TODO: is it actually going to STETH, but then using ETH feed + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); +} + +contract DeployBase is BaseScript { + function run() external broadcast { + AaveV3BasePayload.Adapters memory adapters; + + adapters.usdcAdapter = GovV3Helpers.deployDeterministic(CapAdaptersCodeBase.USDC_ADAPTER_CODE); + adapters.cbEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeBase.cbETH_ADAPTER_CODE + ); + adapters.wstEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeBase.wstETH_ADAPTER_CODE + ); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3BasePayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployBnb.s.sol b/scripts/DeployBnb.s.sol new file mode 100644 index 0000000..be68c0d --- /dev/null +++ b/scripts/DeployBnb.s.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {BNBScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3BNB, AaveV3BNBAssets} from 'aave-address-book/AaveV3Bnb.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {AaveV3BnbPayload} from '../src/contracts/payloads/AaveV3BnbPayload.sol'; + +library CapAdaptersCodeBnb { + bytes public constant USDT_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3BNB.ACL_MANAGER, + AaveV3BNBAssets.USDT_ORACLE, + 'Capped USDT/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3BNB.ACL_MANAGER, + AaveV3BNBAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); +} + +contract DeployBNB is BNBScript { + function run() external broadcast { + AaveV3BnbPayload.Adapters memory adapters; + + adapters.usdtAdapter = GovV3Helpers.deployDeterministic(CapAdaptersCodeBnb.USDT_ADAPTER_CODE); + adapters.usdcAdapter = GovV3Helpers.deployDeterministic(CapAdaptersCodeBnb.USDC_ADAPTER_CODE); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3BnbPayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployEthereum.s.sol b/scripts/DeployEthereum.s.sol new file mode 100644 index 0000000..7032d7b --- /dev/null +++ b/scripts/DeployEthereum.s.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {EthereumScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol'; +import {BaseAggregatorsMainnet} from 'cl-synchronicity-price-adapter/lib/BaseAggregatorsMainnet.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {CbETHPriceCapAdapter, IPriceCapAdapter} from '../src/contracts/CbETHPriceCapAdapter.sol'; +import {RETHPriceCapAdapter} from '../src/contracts/RETHPriceCapAdapter.sol'; +import {WstETHPriceCapAdapter} from '../src/contracts/WstETHPriceCapAdapter.sol'; +import {SDAIPriceCapAdapter} from '../src/contracts/SDAIPriceCapAdapter.sol'; +import {AaveV3EthereumPayload} from '../src/contracts/payloads/AaveV3EthereumPayload.sol'; + +library CapAdaptersCodeEthereum { + bytes public constant USDT_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.USDT_ORACLE, + 'Capped USDT/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant DAI_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.DAI_ORACLE, + 'Capped DAI/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant sDAI_ADAPTER_CODE = + abi.encodePacked( + type(SDAIPriceCapAdapter).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.DAI_ORACLE, + BaseAggregatorsMainnet.SDAI_POT, + 'Capped sDAI / DAI / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant LUSD_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.LUSD_ORACLE, + 'Capped LUSD/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant FRAX_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.FRAX_ORACLE, + 'Capped FRAX/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant crvUSD_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.crvUSD_ORACLE, + 'Capped crvUSD/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant cbETH_ADAPTER_CODE = + abi.encodePacked( + type(CbETHPriceCapAdapter).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + AaveV3EthereumAssets.cbETH_UNDERLYING, + 'Capped cbETH / ETH / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant rETH_ADAPTER_CODE = + abi.encodePacked( + type(RETHPriceCapAdapter).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + AaveV3EthereumAssets.rETH_UNDERLYING, + 'Capped rETH / ETH / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant wstETH_ADAPTER_CODE = + abi.encodePacked( + type(WstETHPriceCapAdapter).creationCode, + abi.encode( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + AaveV2EthereumAssets.stETH_UNDERLYING, + 'Capped wstETH / stETH(ETH) / USD', // TODO: is it actually going to STETH, but then using ETH feed + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); +} + +contract DeployEthereum is EthereumScript { + function run() external broadcast { + AaveV3EthereumPayload.Adapters memory adapters; + + adapters.usdtAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.USDT_ADAPTER_CODE + ); + adapters.usdcAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.USDC_ADAPTER_CODE + ); + adapters.daiAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.DAI_ADAPTER_CODE + ); + adapters.sDaiAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.sDAI_ADAPTER_CODE + ); + adapters.lusdAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.LUSD_ADAPTER_CODE + ); + adapters.fraxAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.FRAX_ADAPTER_CODE + ); + adapters.crvUsdAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.crvUSD_ADAPTER_CODE + ); + adapters.cbEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.cbETH_ADAPTER_CODE + ); + adapters.rEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.rETH_ADAPTER_CODE + ); + adapters.wstEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeEthereum.wstETH_ADAPTER_CODE + ); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3EthereumPayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployGnosis.s.sol b/scripts/DeployGnosis.s.sol new file mode 100644 index 0000000..35f253d --- /dev/null +++ b/scripts/DeployGnosis.s.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {GnosisScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Gnosis, AaveV3GnosisAssets} from 'aave-address-book/AaveV3Gnosis.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {SDAIGnosisPriceCapAdapter} from '../src/contracts/SDAIGnosisPriceCapAdapter.sol'; +import {CLRatePriceCapAdapter, IPriceCapAdapter} from '../src/contracts/CLRatePriceCapAdapter.sol'; +import {AaveV3GnosisPayload} from '../src/contracts/payloads/AaveV3GnosisPayload.sol'; + +library CapAdaptersCodeGnosis { + // TODO: move it to address book + address public constant WSTETH_STETH_AGGREGATOR = 0x0064AC007fF665CF8D0D3Af5E0AD1c26a3f853eA; + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Gnosis.ACL_MANAGER, + AaveV3GnosisAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant WXDAI_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Gnosis.ACL_MANAGER, + AaveV3GnosisAssets.WXDAI_ORACLE, + 'Capped wXDAI/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant sDAI_ADAPTER_CODE = + abi.encodePacked( + type(SDAIGnosisPriceCapAdapter).creationCode, + abi.encode( + AaveV3Gnosis.ACL_MANAGER, + AaveV3GnosisAssets.WXDAI_ORACLE, + AaveV3GnosisAssets.sDAI_UNDERLYING, + 'Capped sDAI / DAI / USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + + bytes public constant wstETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Gnosis.ACL_MANAGER, + AaveV3GnosisAssets.WETH_ORACLE, + WSTETH_STETH_AGGREGATOR, + 'Capped wstETH / stETH(ETH) / USD', // TODO: is it actually going to STETH, but then using ETH feed + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); +} + +contract DeployGnosis is GnosisScript { + function run() external broadcast { + AaveV3GnosisPayload.Adapters memory adapters; + + adapters.usdcAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeGnosis.USDC_ADAPTER_CODE + ); + adapters.wxDaiAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeGnosis.WXDAI_ADAPTER_CODE + ); + adapters.sDaiAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeGnosis.sDAI_ADAPTER_CODE + ); + adapters.wstEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeGnosis.wstETH_ADAPTER_CODE + ); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3GnosisPayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployMetis.s.sol b/scripts/DeployMetis.s.sol new file mode 100644 index 0000000..435818f --- /dev/null +++ b/scripts/DeployMetis.s.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {MetisScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Metis, AaveV3MetisAssets} from 'aave-address-book/AaveV3Metis.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {AaveV3MetisPayload} from '../src/contracts/payloads/AaveV3MetisPayload.sol'; + +library CapAdaptersCodeMetis { + bytes public constant mUSDT_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Metis.ACL_MANAGER, + AaveV3MetisAssets.mUSDT_ORACLE, + 'Capped mUSDT/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant mUSDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Metis.ACL_MANAGER, + AaveV3MetisAssets.mUSDC_ORACLE, + 'Capped mUSDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant mDAI_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Metis.ACL_MANAGER, + AaveV3MetisAssets.mDAI_ORACLE, + 'Capped mDAI/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); +} + +contract DeployMetis is MetisScript { + function run() external broadcast { + AaveV3MetisPayload.Adapters memory adapters; + + adapters.mUsdtAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeMetis.mUSDT_ADAPTER_CODE + ); + adapters.mUsdcAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeMetis.mUSDC_ADAPTER_CODE + ); + adapters.mDaiAdapter = GovV3Helpers.deployDeterministic(CapAdaptersCodeMetis.mDAI_ADAPTER_CODE); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3MetisPayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployOptimism.s.sol b/scripts/DeployOptimism.s.sol new file mode 100644 index 0000000..f27d382 --- /dev/null +++ b/scripts/DeployOptimism.s.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {OptimismScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Optimism, AaveV3OptimismAssets} from 'aave-address-book/AaveV3Optimism.sol'; +import {BaseAggregatorsOptimism} from 'cl-synchronicity-price-adapter/lib/BaseAggregatorsOptimism.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {CLRatePriceCapAdapter, IPriceCapAdapter} from '../src/contracts/CLRatePriceCapAdapter.sol'; +import {AaveV3OptimismPayload} from '../src/contracts/payloads/AaveV3OptimismPayload.sol'; + +library CapAdaptersCodeOptimism { + bytes public constant USDT_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Optimism.ACL_MANAGER, + AaveV3OptimismAssets.USDT_ORACLE, + 'Capped USDT/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Optimism.ACL_MANAGER, + AaveV3OptimismAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant DAI_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Optimism.ACL_MANAGER, + AaveV3OptimismAssets.DAI_ORACLE, + 'Capped DAI/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant LUSD_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Optimism.ACL_MANAGER, + AaveV3OptimismAssets.LUSD_ORACLE, + 'Capped LUSD/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant sUSD_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Optimism.ACL_MANAGER, + AaveV3OptimismAssets.sUSD_ORACLE, + 'Capped sUSD/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant rETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Optimism.ACL_MANAGER, + AaveV3OptimismAssets.WETH_ORACLE, + BaseAggregatorsOptimism.RETH_ETH_AGGREGATOR, + 'Capped rETH / ETH / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant wstETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Optimism.ACL_MANAGER, + AaveV3OptimismAssets.WETH_ORACLE, + BaseAggregatorsOptimism.WSTETH_STETH_AGGREGATOR, + 'Capped wstETH / stETH(ETH) / USD', // TODO: is it actually going to STETH, but then using ETH feed + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); +} + +contract DeployOptimism is OptimismScript { + function run() external broadcast { + AaveV3OptimismPayload.Adapters memory adapters; + + adapters.usdtAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeOptimism.USDT_ADAPTER_CODE + ); + adapters.usdcAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeOptimism.USDC_ADAPTER_CODE + ); + adapters.daiAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeOptimism.DAI_ADAPTER_CODE + ); + adapters.lusdAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeOptimism.LUSD_ADAPTER_CODE + ); + adapters.sUsdAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeOptimism.sUSD_ADAPTER_CODE + ); + adapters.rEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeOptimism.rETH_ADAPTER_CODE + ); + adapters.wstEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodeOptimism.wstETH_ADAPTER_CODE + ); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3OptimismPayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/scripts/DeployPolygon.s.sol b/scripts/DeployPolygon.s.sol new file mode 100644 index 0000000..8bfeeca --- /dev/null +++ b/scripts/DeployPolygon.s.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; +import {PolygonScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV3Polygon, AaveV3PolygonAssets} from 'aave-address-book/AaveV3Polygon.sol'; +import {BaseAggregatorsPolygon} from 'cl-synchronicity-price-adapter/lib/BaseAggregatorsPolygon.sol'; + +import {PriceCapAdapterStable} from '../src/contracts/PriceCapAdapterStable.sol'; +import {CLRatePriceCapAdapter, IPriceCapAdapter} from '../src/contracts/CLRatePriceCapAdapter.sol'; +import {MaticPriceCapAdapter} from '../src/contracts/MaticPriceCapAdapter.sol'; +import {AaveV3PolygonPayload} from '../src/contracts/payloads/AaveV3PolygonPayload.sol'; + +library CapAdaptersCodePolygon { + bytes public constant USDT_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Polygon.ACL_MANAGER, + AaveV3PolygonAssets.USDT_ORACLE, + 'Capped USDT/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + //USDC.e + bytes public constant USDC_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Polygon.ACL_MANAGER, + AaveV3PolygonAssets.USDC_ORACLE, + 'Capped USDC/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + bytes public constant DAI_ADAPTER_CODE = + abi.encodePacked( + type(PriceCapAdapterStable).creationCode, + abi.encode( + AaveV3Polygon.ACL_MANAGER, + AaveV3PolygonAssets.DAI_ORACLE, + 'Capped DAI/USD', + int256(1.1 * 1e8) // TODO: SET + ) + ); + + // TODO: CL feed used + bytes public constant wstETH_ADAPTER_CODE = + abi.encodePacked( + type(CLRatePriceCapAdapter).creationCode, + abi.encode( + AaveV3Polygon.ACL_MANAGER, + AaveV3PolygonAssets.WETH_ORACLE, + BaseAggregatorsPolygon.WSTETH_STETH_AGGREGATOR, + 'Capped wstETH / stETH(ETH) / USD', // TODO: is it actually going to STETH, but then using ETH feed + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant stMATIC_ADAPTER_CODE = + abi.encodePacked( + type(MaticPriceCapAdapter).creationCode, + abi.encode( + AaveV3Polygon.ACL_MANAGER, + AaveV3PolygonAssets.WMATIC_ORACLE, + BaseAggregatorsPolygon.STMATIC_RATE_PROVIDER, + 'Capped stMATIC / MATIC / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); + bytes public constant MaticX_ADAPTER_CODE = + abi.encodePacked( + type(MaticPriceCapAdapter).creationCode, + abi.encode( + AaveV3Polygon.ACL_MANAGER, + AaveV3PolygonAssets.WMATIC_ORACLE, + BaseAggregatorsPolygon.MATICX_RATE_PROVIDER, + 'Capped MaticX / MATIC / USD', + 7 days, // TODO: SET + IPriceCapAdapter.PriceCapUpdateParams({ + snapshotRatio: 0, + snapshotTimestamp: 0, + maxYearlyRatioGrowthPercent: 0 + }) + ) + ); +} + +contract DeployPolygon is PolygonScript { + function run() external broadcast { + AaveV3PolygonPayload.Adapters memory adapters; + + adapters.usdtAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodePolygon.USDT_ADAPTER_CODE + ); + adapters.usdcAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodePolygon.USDC_ADAPTER_CODE + ); + adapters.daiAdapter = GovV3Helpers.deployDeterministic(CapAdaptersCodePolygon.DAI_ADAPTER_CODE); + adapters.wstEthAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodePolygon.wstETH_ADAPTER_CODE + ); + adapters.stMaticAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodePolygon.stMATIC_ADAPTER_CODE + ); + adapters.maticXAdapter = GovV3Helpers.deployDeterministic( + CapAdaptersCodePolygon.MaticX_ADAPTER_CODE + ); + + GovV3Helpers.deployDeterministic( + abi.encode(type(AaveV3PolygonPayload).creationCode, abi.encode(adapters)) + ); + } +} diff --git a/src/contracts/CLRatePriceCapAdapter.sol b/src/contracts/CLRatePriceCapAdapter.sol new file mode 100644 index 0000000..a490220 --- /dev/null +++ b/src/contracts/CLRatePriceCapAdapter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {IChainlinkAggregator} from 'cl-synchronicity-price-adapter/interfaces/IChainlinkAggregator.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; + +/** + * @title CLRatePriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (lstASSET / USD) pair by using + * @notice Chainlink data feeds for (ASSET / USD) and (lstASSET / ASSET). + */ +contract CLRatePriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param assetToBaseAggregatorAddress the address of (ASSET / USD) feed + * @param ratioProviderAddress the address of the (lstASSET / ASSET) ratio feed + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address assetToBaseAggregatorAddress, + address ratioProviderAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + assetToBaseAggregatorAddress, + ratioProviderAddress, + pairName, + IChainlinkAggregator(ratioProviderAddress).decimals(), + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return IChainlinkAggregator(RATIO_PROVIDER).latestAnswer(); + } +} diff --git a/src/contracts/CbETHPriceCapAdapter.sol b/src/contracts/CbETHPriceCapAdapter.sol new file mode 100644 index 0000000..1bfd42c --- /dev/null +++ b/src/contracts/CbETHPriceCapAdapter.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {ICbEthRateProvider} from 'cl-synchronicity-price-adapter/interfaces/ICbEthRateProvider.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; + +/** + * @title CbETHPriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (cbETH / USD) pair by using + * @notice Chainlink data feed for (ETH / USD) and (cbETH / ETH) ratio. + */ +// TODO: current implementation uses ratio from the cbETH contract, but existing production oracle uses CL feed instead +// TODO: should be clarified before moving forward +contract CbETHPriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param cbETHToBaseAggregatorAddress the address of cbETH / BASE feed + * @param ratioProviderAddress the address of the (cbETH / ETH) ratio provider + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address cbETHToBaseAggregatorAddress, + address ratioProviderAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + cbETHToBaseAggregatorAddress, + ratioProviderAddress, + pairName, + 18, + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return int256(ICbEthRateProvider(RATIO_PROVIDER).exchangeRate()); + } +} diff --git a/src/contracts/MaticPriceCapAdapter.sol b/src/contracts/MaticPriceCapAdapter.sol new file mode 100644 index 0000000..8b04c7d --- /dev/null +++ b/src/contracts/MaticPriceCapAdapter.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {IMaticRateProvider} from 'cl-synchronicity-price-adapter/interfaces/IMaticRateProvider.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; + +/** + * @title MaticPriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (lst Matic / USD) pair by using + * @notice Chainlink data feed for (MATIC / USD) and (lst Matic / MATIC) ratio. + * @notice can be used as it is for stMatic and MaticX on Polygon network + */ +contract MaticPriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param maticToBaseAggregatorAddress the address of MATIC / USD feed + * @param ratioProviderAddress the address of (lst Matic / MATIC) pair ratio feed + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address maticToBaseAggregatorAddress, + address ratioProviderAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + maticToBaseAggregatorAddress, + ratioProviderAddress, + pairName, + 18, + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return int256(IMaticRateProvider(RATIO_PROVIDER).getRate()); + } +} diff --git a/src/contracts/PriceCapAdapterBase.sol b/src/contracts/PriceCapAdapterBase.sol new file mode 100644 index 0000000..641215e --- /dev/null +++ b/src/contracts/PriceCapAdapterBase.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IPriceCapAdapter, ICLSynchronicityPriceAdapter, IACLManager, IChainlinkAggregator} from '../interfaces/IPriceCapAdapter.sol'; + +/** + * @title PriceCapAdapterBase + * @author BGD Labs + * @notice Price adapter to cap the price of the underlying asset. + */ +abstract contract PriceCapAdapterBase is IPriceCapAdapter { + /// @inheritdoc IPriceCapAdapter + uint256 public constant PERCENTAGE_FACTOR = 1e4; + + /// @inheritdoc IPriceCapAdapter + uint256 public constant MINIMAL_RATIO_INCREASE_LIFETIME = 3; + + /// @inheritdoc IPriceCapAdapter + uint256 public constant SECONDS_PER_YEAR = 365 days; + + /// @inheritdoc IPriceCapAdapter + IChainlinkAggregator public immutable BASE_TO_USD_AGGREGATOR; + + /// @inheritdoc IPriceCapAdapter + IACLManager public immutable ACL_MANAGER; + + /// @inheritdoc IPriceCapAdapter + address public immutable RATIO_PROVIDER; + + /// @inheritdoc IPriceCapAdapter + uint8 public immutable DECIMALS; + + /// @inheritdoc IPriceCapAdapter + uint8 public immutable RATIO_DECIMALS; + + /// @inheritdoc IPriceCapAdapter + uint48 public immutable MINIMUM_SNAPSHOT_DELAY; + + /** + * @notice Description of the pair + */ + string private _description; + + /** + * @notice Ratio at the time of snapshot + */ + uint104 private _snapshotRatio; + + /** + * @notice Timestamp at the time of snapshot + */ + uint48 private _snapshotTimestamp; + + /** + * @notice Ratio growth per second + */ + uint104 private _maxRatioGrowthPerSecond; + + /** + * @notice Max yearly growth percent + */ + uint16 private _maxYearlyRatioGrowthPercent; + + /** + * @param aclManager ACL manager contract + * @param baseAggregatorAddress the address of (underlyingAsset / USD) price feed + * @param ratioProviderAddress the address of (lst /underlyingAsset) ratio feed + * @param pairDescription the capped (lstAsset / underlyingAsset) pair description + * @param ratioDecimals the number of decimal places of the (lstAsset / underlyingAsset) ratio feed + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint8 ratioDecimals, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) { + if (address(aclManager) == address(0)) { + revert ACLManagerIsZeroAddress(); + } + ACL_MANAGER = aclManager; + BASE_TO_USD_AGGREGATOR = IChainlinkAggregator(baseAggregatorAddress); + RATIO_PROVIDER = ratioProviderAddress; + DECIMALS = BASE_TO_USD_AGGREGATOR.decimals(); + RATIO_DECIMALS = ratioDecimals; + MINIMUM_SNAPSHOT_DELAY = minimumSnapshotDelay; + + _description = pairDescription; + + _setCapParameters(priceCapParams); + } + + /// @inheritdoc ICLSynchronicityPriceAdapter + function description() external view returns (string memory) { + return _description; + } + + /// @inheritdoc ICLSynchronicityPriceAdapter + function decimals() external view returns (uint8) { + return DECIMALS; + } + + /// @inheritdoc IPriceCapAdapter + function getSnapshotRatio() external view returns (uint256) { + return _snapshotRatio; + } + + /// @inheritdoc IPriceCapAdapter + function getSnapshotTimestamp() external view returns (uint256) { + return _snapshotTimestamp; + } + + /// @inheritdoc IPriceCapAdapter + function getMaxYearlyGrowthRatePercent() external view returns (uint256) { + return _maxYearlyRatioGrowthPercent; + } + + /// @inheritdoc IPriceCapAdapter + function getMaxRatioGrowthPerSecond() external view returns (uint256) { + return _maxRatioGrowthPerSecond; + } + + /// @inheritdoc IPriceCapAdapter + function setCapParameters(PriceCapUpdateParams memory priceCapParams) external { + if (!ACL_MANAGER.isRiskAdmin(msg.sender) && !ACL_MANAGER.isPoolAdmin(msg.sender)) { + revert CallerIsNotRiskOrPoolAdmin(); + } + + _setCapParameters(priceCapParams); + } + + /// @inheritdoc ICLSynchronicityPriceAdapter + function latestAnswer() external view override returns (int256) { + // get the current lst to underlying ratio + int256 currentRatio = getRatio(); + // get the base price + int256 basePrice = BASE_TO_USD_AGGREGATOR.latestAnswer(); + + if (basePrice <= 0 || currentRatio <= 0) { + return 0; + } + + // calculate the ratio based on snapshot ratio and max growth rate + int256 maxRatio = int256( + _snapshotRatio + _maxRatioGrowthPerSecond * (block.timestamp - _snapshotTimestamp) + ); + + if (maxRatio < currentRatio) { + currentRatio = maxRatio; + } + + // calculate the price of the underlying asset + int256 price = (basePrice * currentRatio) / int256(10 ** RATIO_DECIMALS); + + return price; + } + + /** + * @notice Updates price cap parameters + * @param priceCapParams parameters to set price cap + */ + function _setCapParameters(PriceCapUpdateParams memory priceCapParams) internal { + // if snapshot ratio is 0 then growth will not work as expected + if (priceCapParams.snapshotRatio == 0) { + revert SnapshotRatioIsZero(); + } + + // new snapshot timestamp should be gt then stored one, but not gt then timestamp of the current block + if ( + _snapshotTimestamp >= priceCapParams.snapshotTimestamp || + priceCapParams.snapshotTimestamp > block.timestamp - MINIMUM_SNAPSHOT_DELAY + ) { + revert InvalidRatioTimestamp(priceCapParams.snapshotTimestamp); + } + _snapshotRatio = priceCapParams.snapshotRatio; + _snapshotTimestamp = priceCapParams.snapshotTimestamp; + _maxYearlyRatioGrowthPercent = priceCapParams.maxYearlyRatioGrowthPercent; + + _maxRatioGrowthPerSecond = uint104( + (uint256(priceCapParams.snapshotRatio) * priceCapParams.maxYearlyRatioGrowthPercent) / + PERCENTAGE_FACTOR / + SECONDS_PER_YEAR + ); + + // if the ratio on the current growth speed can overflow less then in a MINIMAL_RATIO_INCREASE_LIFETIME years, revert + if ( + uint256(_snapshotRatio) + + (_maxRatioGrowthPerSecond * SECONDS_PER_YEAR * MINIMAL_RATIO_INCREASE_LIFETIME) > + type(uint104).max + ) { + revert SnapshotMayOverflowSoon( + priceCapParams.snapshotRatio, + priceCapParams.maxYearlyRatioGrowthPercent + ); + } + + emit CapParametersUpdated( + priceCapParams.snapshotRatio, + priceCapParams.snapshotTimestamp, + _maxRatioGrowthPerSecond, + priceCapParams.maxYearlyRatioGrowthPercent + ); + } + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view virtual returns (int256); +} diff --git a/src/contracts/PriceCapAdapterStable.sol b/src/contracts/PriceCapAdapterStable.sol new file mode 100644 index 0000000..dec1b9e --- /dev/null +++ b/src/contracts/PriceCapAdapterStable.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IPriceCapAdapterStable, ICLSynchronicityPriceAdapter, IACLManager, IChainlinkAggregator} from '../interfaces/IPriceCapAdapterStable.sol'; + +/** + * @title PriceCapAdapterStable + * @author BGD Labs + * @notice Price adapter to cap the price of the USD pegged assets + */ +contract PriceCapAdapterStable is IPriceCapAdapterStable { + /// @inheritdoc IPriceCapAdapterStable + IChainlinkAggregator public immutable ASSET_TO_USD_AGGREGATOR; + + /// @inheritdoc IPriceCapAdapterStable + IACLManager public immutable ACL_MANAGER; + + /// @inheritdoc ICLSynchronicityPriceAdapter + uint8 public decimals; + + /// @inheritdoc ICLSynchronicityPriceAdapter + string public description; + + int256 internal _priceCap; + + /** + * @param aclManager ACL manager contract + * @param assetToUsdAggregator the address of (underlyingAsset / USD) price feed + * @param adapterDescription the capped (lstAsset / underlyingAsset) pair description + * @param priceCap the price cap + */ + constructor( + IACLManager aclManager, + IChainlinkAggregator assetToUsdAggregator, + string memory adapterDescription, + int256 priceCap + ) { + if (address(aclManager) == address(0)) { + revert ACLManagerIsZeroAddress(); + } + + ASSET_TO_USD_AGGREGATOR = assetToUsdAggregator; + ACL_MANAGER = aclManager; + description = adapterDescription; + decimals = assetToUsdAggregator.decimals(); + + _setPriceCap(priceCap); + } + + /// @inheritdoc ICLSynchronicityPriceAdapter + function latestAnswer() external view override returns (int256) { + int256 basePrice = ASSET_TO_USD_AGGREGATOR.latestAnswer(); + int256 priceCap = _priceCap; + + if (basePrice > priceCap) { + return priceCap; + } + + return basePrice; + } + + /// @inheritdoc IPriceCapAdapterStable + function setPriceCap(int256 priceCap) external { + if (!ACL_MANAGER.isRiskAdmin(msg.sender) && !ACL_MANAGER.isPoolAdmin(msg.sender)) { + revert CallerIsNotRiskOrPoolAdmin(); + } + + _setPriceCap(priceCap); + } + + /** + * @notice Updates price cap + * @param priceCap the new price cap + */ + function _setPriceCap(int256 priceCap) internal { + if (priceCap < 0) { + revert NegativePrice(priceCap); + } + + _priceCap = priceCap; + + emit PriceCapUpdated(priceCap); + } +} diff --git a/src/contracts/RETHPriceCapAdapter.sol b/src/contracts/RETHPriceCapAdapter.sol new file mode 100644 index 0000000..8acd8ae --- /dev/null +++ b/src/contracts/RETHPriceCapAdapter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {IrETH} from 'cl-synchronicity-price-adapter/interfaces/IrETH.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; + +/** + * @title RETHPriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (rETH / USD) pair by using + * @notice Chainlink data feed for (ETH / USD) and (rETH / ETH) ratio. + */ +contract RETHPriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param rETHToBaseAggregatorAddress the address of (rETH / USD) feed + * @param rETHAddress the address of the rETH token, the (rETH / ETH) ratio feed + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address rETHToBaseAggregatorAddress, + address rETHAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + rETHToBaseAggregatorAddress, + rETHAddress, + pairName, + 18, + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return int256(IrETH(RATIO_PROVIDER).getExchangeRate()); + } +} diff --git a/src/contracts/SAvaxPriceCapAdapter.sol b/src/contracts/SAvaxPriceCapAdapter.sol new file mode 100644 index 0000000..40d4c2c --- /dev/null +++ b/src/contracts/SAvaxPriceCapAdapter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; +import {ISAvax} from '../interfaces/ISAvax.sol'; + +/** + * @title SAvaxPriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (sAVAX / USD) pair by using + * @notice Chainlink data feed for (AVAX / USD) and (sAVAX / AVAX) ratio. + */ +contract SAvaxPriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param avaxToBaseAggregatorAddress the address of (AVAX / USD) feed + * @param sAVAXAddress the address of the sAVAX token, the (sAVAX / AVAX) ratio feed + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address avaxToBaseAggregatorAddress, + address sAVAXAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + avaxToBaseAggregatorAddress, + sAVAXAddress, + pairName, + 18, + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return int256(ISAvax(RATIO_PROVIDER).getPooledAvaxByShares(10 ** RATIO_DECIMALS)); + } +} diff --git a/src/contracts/SDAIGnosisPriceCapAdapter.sol b/src/contracts/SDAIGnosisPriceCapAdapter.sol new file mode 100644 index 0000000..98915f9 --- /dev/null +++ b/src/contracts/SDAIGnosisPriceCapAdapter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {IERC4626} from 'forge-std/interfaces/IERC4626.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; + +/** + * @title SDAIGnosisPriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (sDAI / USD) pair by using + * @notice Chainlink data feed for (DAI / USD) and (sDAI / DAI) ratio. + */ +contract SDAIGnosisPriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param daiToBaseAggregatorAddress the address of (DAI / USD) feed + * @param sDaiAddress the address of the sDAI, used as the (sDAI / DAI) ratio feed + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address daiToBaseAggregatorAddress, + address sDaiAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + daiToBaseAggregatorAddress, + sDaiAddress, + pairName, + 18, + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return int256(IERC4626(RATIO_PROVIDER).convertToAssets(10 ** RATIO_DECIMALS)); + } +} diff --git a/src/contracts/SDAIPriceCapAdapter.sol b/src/contracts/SDAIPriceCapAdapter.sol new file mode 100644 index 0000000..5be0a50 --- /dev/null +++ b/src/contracts/SDAIPriceCapAdapter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {IPot} from 'cl-synchronicity-price-adapter/interfaces/IPot.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; + +/** + * @title SDAIPriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (sDAI / USD) pair by using + * @notice Chainlink data feed for (DAI / USD) and (sDAI / DAI) ratio. + */ +contract SDAIPriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param daiToBaseAggregatorAddress the address of (DAI / USD) feed + * @param potAddress the address of the sDAI pot, used the (sDAI / DAI) ratio feed + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address daiToBaseAggregatorAddress, + address potAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + daiToBaseAggregatorAddress, + potAddress, + pairName, + 27, + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return int256(IPot(RATIO_PROVIDER).chi()); + } +} diff --git a/src/contracts/WstETHPriceCapAdapter.sol b/src/contracts/WstETHPriceCapAdapter.sol new file mode 100644 index 0000000..8a8aced --- /dev/null +++ b/src/contracts/WstETHPriceCapAdapter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {IStETH} from 'cl-synchronicity-price-adapter/interfaces/IStETH.sol'; + +import {PriceCapAdapterBase, IPriceCapAdapter} from './PriceCapAdapterBase.sol'; + +/** + * @title WstETHPriceCapAdapter + * @author BGD Labs + * @notice Price capped adapter to calculate price of (wstETH / USD) pair by using + * @notice Chainlink data feed for (ETH / USD) and (wstETH / stETH) ratio. + */ +contract WstETHPriceCapAdapter is PriceCapAdapterBase { + /** + * @param aclManager ACL manager contract + * @param ethToBaseAggregatorAddress the address of (ETH / USD) feed + * @param stEthAddress the address of the stETH contract, the (wStETH / ETH) ratio feed + * @param pairName name identifier + * @param minimumSnapshotDelay minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + * @param priceCapParams parameters to set price cap + */ + constructor( + IACLManager aclManager, + address ethToBaseAggregatorAddress, + address stEthAddress, + string memory pairName, + uint48 minimumSnapshotDelay, + PriceCapUpdateParams memory priceCapParams + ) + PriceCapAdapterBase( + aclManager, + ethToBaseAggregatorAddress, + stEthAddress, + pairName, + 18, + minimumSnapshotDelay, + priceCapParams + ) + {} + + /// @inheritdoc IPriceCapAdapter + function getRatio() public view override returns (int256) { + return int256(IStETH(RATIO_PROVIDER).getPooledEthByShares(10 ** RATIO_DECIMALS)); + } +} diff --git a/src/contracts/payloads/AaveV3ArbitrumPayload.sol b/src/contracts/payloads/AaveV3ArbitrumPayload.sol new file mode 100644 index 0000000..dccaaa4 --- /dev/null +++ b/src/contracts/payloads/AaveV3ArbitrumPayload.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadArbitrum, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadArbitrum.sol'; +import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; + +contract AaveV3ArbitrumPayload is AaveV3PayloadArbitrum { + struct Adapters { + address usdtAdapter; + address usdcAdapter; + address daiAdapter; + address lusdAdapter; + address fraxAdapter; + address rEthAdapter; + address wstEthAdapter; + } + + address public immutable USDT_ADAPTER; + address public immutable USDC_ADAPTER; + address public immutable DAI_ADAPTER; + address public immutable LUSD_ADAPTER; + address public immutable FRAX_ADAPTER; + address public immutable rETH_ADAPTER; + address public immutable wstETH_ADAPTER; + + constructor(Adapters memory adapters) { + USDT_ADAPTER = adapters.usdtAdapter; + USDC_ADAPTER = adapters.usdcAdapter; + DAI_ADAPTER = adapters.daiAdapter; + LUSD_ADAPTER = adapters.lusdAdapter; + FRAX_ADAPTER = adapters.fraxAdapter; + rETH_ADAPTER = adapters.rEthAdapter; + wstETH_ADAPTER = adapters.wstEthAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](8); + updates[0].asset = AaveV3ArbitrumAssets.USDT_UNDERLYING; + updates[0].priceFeed = USDT_ADAPTER; + + updates[1].asset = AaveV3ArbitrumAssets.USDCn_UNDERLYING; + updates[1].priceFeed = USDC_ADAPTER; + + updates[2].asset = AaveV3ArbitrumAssets.USDC_UNDERLYING; + updates[2].priceFeed = USDC_ADAPTER; + + updates[3].asset = AaveV3ArbitrumAssets.DAI_UNDERLYING; + updates[3].priceFeed = DAI_ADAPTER; + + updates[4].asset = AaveV3ArbitrumAssets.LUSD_UNDERLYING; + updates[4].priceFeed = LUSD_ADAPTER; + + updates[5].asset = AaveV3ArbitrumAssets.FRAX_UNDERLYING; + updates[5].priceFeed = FRAX_ADAPTER; + + updates[6].asset = AaveV3ArbitrumAssets.rETH_UNDERLYING; + updates[6].priceFeed = rETH_ADAPTER; + + updates[7].asset = AaveV3ArbitrumAssets.wstETH_UNDERLYING; + updates[7].priceFeed = wstETH_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3AvalanchePayload.sol b/src/contracts/payloads/AaveV3AvalanchePayload.sol new file mode 100644 index 0000000..81acc21 --- /dev/null +++ b/src/contracts/payloads/AaveV3AvalanchePayload.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadAvalanche, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadAvalanche.sol'; +import {AaveV3AvalancheAssets} from 'aave-address-book/AaveV3Avalanche.sol'; + +contract AaveV3AvalanchePayload is AaveV3PayloadAvalanche { + struct Adapters { + address usdtAdapter; + address usdcAdapter; + address daieAdapter; + address fraxAdapter; + address sAvaxAdapter; + } + + address public immutable USDt_ADAPTER; + address public immutable USDC_ADAPTER; + address public immutable DAIe_ADAPTER; + address public immutable FRAX_ADAPTER; + address public immutable sAVAX_ADAPTER; + + constructor(Adapters memory adapters) { + USDt_ADAPTER = adapters.usdtAdapter; + USDC_ADAPTER = adapters.usdcAdapter; + DAIe_ADAPTER = adapters.daieAdapter; + FRAX_ADAPTER = adapters.fraxAdapter; + sAVAX_ADAPTER = adapters.sAvaxAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](5); + updates[0].asset = AaveV3AvalancheAssets.USDt_UNDERLYING; + updates[0].priceFeed = USDt_ADAPTER; + + updates[1].asset = AaveV3AvalancheAssets.USDC_UNDERLYING; + updates[1].priceFeed = USDC_ADAPTER; + + updates[2].asset = AaveV3AvalancheAssets.DAIe_UNDERLYING; + updates[2].priceFeed = DAIe_ADAPTER; + + updates[3].asset = AaveV3AvalancheAssets.FRAX_UNDERLYING; + updates[3].priceFeed = FRAX_ADAPTER; + + updates[4].asset = AaveV3AvalancheAssets.sAVAX_UNDERLYING; + updates[4].priceFeed = sAVAX_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3BasePayload.sol b/src/contracts/payloads/AaveV3BasePayload.sol new file mode 100644 index 0000000..1ece18f --- /dev/null +++ b/src/contracts/payloads/AaveV3BasePayload.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadBase, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadBase.sol'; +import {AaveV3BaseAssets} from 'aave-address-book/AaveV3Base.sol'; + +contract AaveV3BasePayload is AaveV3PayloadBase { + struct Adapters { + address usdcAdapter; + address cbEthAdapter; + address wstEthAdapter; + } + + address public immutable USDC_ADAPTER; + address public immutable cbETH_ADAPTER; + address public immutable wstETH_ADAPTER; + + constructor(Adapters memory adapters) { + USDC_ADAPTER = adapters.usdcAdapter; + cbETH_ADAPTER = adapters.cbEthAdapter; + wstETH_ADAPTER = adapters.wstEthAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](4); + updates[0].asset = AaveV3BaseAssets.USDbC_UNDERLYING; + updates[0].priceFeed = USDC_ADAPTER; + + updates[1].asset = AaveV3BaseAssets.USDC_UNDERLYING; + updates[1].priceFeed = USDC_ADAPTER; + + updates[2].asset = AaveV3BaseAssets.cbETH_UNDERLYING; + updates[2].priceFeed = cbETH_ADAPTER; + + updates[3].asset = AaveV3BaseAssets.wstETH_UNDERLYING; + updates[3].priceFeed = wstETH_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3BnbPayload.sol b/src/contracts/payloads/AaveV3BnbPayload.sol new file mode 100644 index 0000000..4c30326 --- /dev/null +++ b/src/contracts/payloads/AaveV3BnbPayload.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadBnb, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadBnb.sol'; +import {AaveV3BNBAssets} from 'aave-address-book/AaveV3BNB.sol'; + +contract AaveV3BnbPayload is AaveV3PayloadBnb { + struct Adapters { + address usdtAdapter; + address usdcAdapter; + } + + address public immutable USDT_ADAPTER; + address public immutable USDC_ADAPTER; + + constructor(Adapters memory adapters) { + USDT_ADAPTER = adapters.usdtAdapter; + USDC_ADAPTER = adapters.usdcAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](3); + updates[0].asset = AaveV3BNBAssets.USDT_UNDERLYING; + updates[0].priceFeed = USDT_ADAPTER; + + updates[1].asset = AaveV3BNBAssets.USDC_UNDERLYING; + updates[1].priceFeed = USDC_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3EthereumPayload.sol b/src/contracts/payloads/AaveV3EthereumPayload.sol new file mode 100644 index 0000000..8ca202c --- /dev/null +++ b/src/contracts/payloads/AaveV3EthereumPayload.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadEthereum, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadEthereum.sol'; +import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; + +contract AaveV3EthereumPayload is AaveV3PayloadEthereum { + struct Adapters { + address usdtAdapter; + address usdcAdapter; + address daiAdapter; + address sDaiAdapter; + address lusdAdapter; + address fraxAdapter; + address crvUsdAdapter; + address cbEthAdapter; + address rEthAdapter; + address wstEthAdapter; + } + + address public immutable USDT_ADAPTER; + address public immutable USDC_ADAPTER; + address public immutable DAI_ADAPTER; + address public immutable sDAI_ADAPTER; + address public immutable LUSD_ADAPTER; + address public immutable FRAX_ADAPTER; + address public immutable crvUSD_ADAPTER; + address public immutable cbETH_ADAPTER; + address public immutable rETH_ADAPTER; + address public immutable wstETH_ADAPTER; + + constructor(Adapters memory adapters) { + USDT_ADAPTER = adapters.usdtAdapter; + USDC_ADAPTER = adapters.usdcAdapter; + DAI_ADAPTER = adapters.daiAdapter; + sDAI_ADAPTER = adapters.sDaiAdapter; + LUSD_ADAPTER = adapters.lusdAdapter; + FRAX_ADAPTER = adapters.fraxAdapter; + crvUSD_ADAPTER = adapters.crvUsdAdapter; + cbETH_ADAPTER = adapters.cbEthAdapter; + rETH_ADAPTER = adapters.rEthAdapter; + wstETH_ADAPTER = adapters.wstEthAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](10); + updates[0].asset = AaveV3EthereumAssets.USDT_UNDERLYING; + updates[0].priceFeed = USDT_ADAPTER; + + updates[1].asset = AaveV3EthereumAssets.USDC_UNDERLYING; + updates[1].priceFeed = USDC_ADAPTER; + + updates[2].asset = AaveV3EthereumAssets.DAI_UNDERLYING; + updates[2].priceFeed = DAI_ADAPTER; + + updates[3].asset = AaveV3EthereumAssets.sDAI_UNDERLYING; + updates[3].priceFeed = sDAI_ADAPTER; + + updates[4].asset = AaveV3EthereumAssets.LUSD_UNDERLYING; + updates[4].priceFeed = LUSD_ADAPTER; + + updates[5].asset = AaveV3EthereumAssets.FRAX_UNDERLYING; + updates[5].priceFeed = FRAX_ADAPTER; + + updates[6].asset = AaveV3EthereumAssets.crvUSD_UNDERLYING; + updates[6].priceFeed = crvUSD_ADAPTER; + + updates[7].asset = AaveV3EthereumAssets.cbETH_UNDERLYING; + updates[7].priceFeed = cbETH_ADAPTER; + + updates[8].asset = AaveV3EthereumAssets.rETH_UNDERLYING; + updates[8].priceFeed = rETH_ADAPTER; + + updates[9].asset = AaveV3EthereumAssets.wstETH_UNDERLYING; + updates[9].priceFeed = wstETH_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3GnosisPayload.sol b/src/contracts/payloads/AaveV3GnosisPayload.sol new file mode 100644 index 0000000..f84056d --- /dev/null +++ b/src/contracts/payloads/AaveV3GnosisPayload.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadGnosis, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadGnosis.sol'; +import {AaveV3GnosisAssets} from 'aave-address-book/AaveV3Gnosis.sol'; + +//wstETH, sDAI, xDAI, USDC, +contract AaveV3GnosisPayload is AaveV3PayloadGnosis { + struct Adapters { + address usdcAdapter; + address wxDaiAdapter; + address sDaiAdapter; + address wstEthAdapter; + } + + address public immutable USDC_ADAPTER; + address public immutable WXDAI_ADAPTER; + address public immutable sDAI_ADAPTER; + address public immutable wstETH_ADAPTER; + + constructor(Adapters memory adapters) { + USDC_ADAPTER = adapters.usdcAdapter; + WXDAI_ADAPTER = adapters.wxDaiAdapter; + sDAI_ADAPTER = adapters.sDaiAdapter; + wstETH_ADAPTER = adapters.wstEthAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](4); + updates[0].asset = AaveV3GnosisAssets.USDC_UNDERLYING; + updates[0].priceFeed = USDC_ADAPTER; + + updates[1].asset = AaveV3GnosisAssets.WXDAI_UNDERLYING; + updates[1].priceFeed = WXDAI_ADAPTER; + + updates[2].asset = AaveV3GnosisAssets.sDAI_UNDERLYING; + updates[2].priceFeed = sDAI_ADAPTER; + + updates[3].asset = AaveV3GnosisAssets.wstETH_UNDERLYING; + updates[3].priceFeed = wstETH_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3MetisPayload.sol b/src/contracts/payloads/AaveV3MetisPayload.sol new file mode 100644 index 0000000..cb758b0 --- /dev/null +++ b/src/contracts/payloads/AaveV3MetisPayload.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadMetis, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadMetis.sol'; +import {AaveV3MetisAssets} from 'aave-address-book/AaveV3Metis.sol'; + +contract AaveV3MetisPayload is AaveV3PayloadMetis { + struct Adapters { + address mUsdtAdapter; + address mUsdcAdapter; + address mDaiAdapter; + } + + address public immutable mUSDT_ADAPTER; + address public immutable mUSDC_ADAPTER; + address public immutable mDAI_ADAPTER; + + constructor(Adapters memory adapters) { + mUSDT_ADAPTER = adapters.mUsdtAdapter; + mUSDC_ADAPTER = adapters.mUsdcAdapter; + mDAI_ADAPTER = adapters.mDaiAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](3); + updates[0].asset = AaveV3MetisAssets.mUSDT_UNDERLYING; + updates[0].priceFeed = mUSDT_ADAPTER; + + updates[1].asset = AaveV3MetisAssets.mUSDC_UNDERLYING; + updates[1].priceFeed = mUSDC_ADAPTER; + + updates[2].asset = AaveV3MetisAssets.mDAI_UNDERLYING; + updates[2].priceFeed = mDAI_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3OptimismPayload.sol b/src/contracts/payloads/AaveV3OptimismPayload.sol new file mode 100644 index 0000000..ac945cd --- /dev/null +++ b/src/contracts/payloads/AaveV3OptimismPayload.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadOptimism, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadOptimism.sol'; +import {AaveV3OptimismAssets} from 'aave-address-book/AaveV3Optimism.sol'; + +contract AaveV3OptimismPayload is AaveV3PayloadOptimism { + struct Adapters { + address usdtAdapter; + address usdcAdapter; + address daiAdapter; + address lusdAdapter; + address sUsdAdapter; + address rEthAdapter; + address wstEthAdapter; + } + + address public immutable USDT_ADAPTER; + address public immutable USDC_ADAPTER; + address public immutable DAI_ADAPTER; + address public immutable LUSD_ADAPTER; + address public immutable sUSD_ADAPTER; + address public immutable rETH_ADAPTER; + address public immutable wstETH_ADAPTER; + + constructor(Adapters memory adapters) { + USDT_ADAPTER = adapters.usdtAdapter; + USDC_ADAPTER = adapters.usdcAdapter; + DAI_ADAPTER = adapters.daiAdapter; + LUSD_ADAPTER = adapters.lusdAdapter; + sUSD_ADAPTER = adapters.sUsdAdapter; + rETH_ADAPTER = adapters.rEthAdapter; + wstETH_ADAPTER = adapters.wstEthAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](8); + updates[0].asset = AaveV3OptimismAssets.USDT_UNDERLYING; + updates[0].priceFeed = USDT_ADAPTER; + + updates[1].asset = AaveV3OptimismAssets.USDCn_UNDERLYING; + updates[1].priceFeed = USDC_ADAPTER; + + updates[2].asset = AaveV3OptimismAssets.USDC_UNDERLYING; + updates[2].priceFeed = USDC_ADAPTER; + + updates[3].asset = AaveV3OptimismAssets.DAI_UNDERLYING; + updates[3].priceFeed = DAI_ADAPTER; + + updates[4].asset = AaveV3OptimismAssets.LUSD_UNDERLYING; + updates[4].priceFeed = LUSD_ADAPTER; + + updates[5].asset = AaveV3OptimismAssets.sUSD_UNDERLYING; + updates[5].priceFeed = sUSD_ADAPTER; + + updates[6].asset = AaveV3OptimismAssets.rETH_UNDERLYING; + updates[6].priceFeed = rETH_ADAPTER; + + updates[7].asset = AaveV3OptimismAssets.wstETH_UNDERLYING; + updates[7].priceFeed = wstETH_ADAPTER; + + return updates; + } +} diff --git a/src/contracts/payloads/AaveV3PolygonPayload.sol b/src/contracts/payloads/AaveV3PolygonPayload.sol new file mode 100644 index 0000000..f4841b3 --- /dev/null +++ b/src/contracts/payloads/AaveV3PolygonPayload.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3PayloadPolygon, IEngine} from 'aave-helpers/v3-config-engine/AaveV3PayloadPolygon.sol'; +import {AaveV3PolygonAssets} from 'aave-address-book/AaveV3Polygon.sol'; + +contract AaveV3PolygonPayload is AaveV3PayloadPolygon { + struct Adapters { + address usdtAdapter; + address usdcAdapter; + address daiAdapter; + address wstEthAdapter; + address stMaticAdapter; + address maticXAdapter; + } + + address public immutable USDT_ADAPTER; + address public immutable USDC_ADAPTER; + address public immutable DAI_ADAPTER; + address public immutable wstETH_ADAPTER; + address public immutable stMATIC_ADAPTER; + address public immutable MaticX_ADAPTER; + + constructor(Adapters memory adapters) { + USDT_ADAPTER = adapters.usdtAdapter; + USDC_ADAPTER = adapters.usdcAdapter; + DAI_ADAPTER = adapters.daiAdapter; + wstETH_ADAPTER = adapters.wstEthAdapter; + stMATIC_ADAPTER = adapters.stMaticAdapter; + MaticX_ADAPTER = adapters.maticXAdapter; + } + + function priceFeedsUpdates() public view override returns (IEngine.PriceFeedUpdate[] memory) { + IEngine.PriceFeedUpdate[] memory updates = new IEngine.PriceFeedUpdate[](7); + updates[0].asset = AaveV3PolygonAssets.USDT_UNDERLYING; + updates[0].priceFeed = USDT_ADAPTER; + + updates[1].asset = AaveV3PolygonAssets.USDCn_UNDERLYING; + updates[1].priceFeed = USDC_ADAPTER; + + updates[2].asset = AaveV3PolygonAssets.USDC_UNDERLYING; + updates[2].priceFeed = USDC_ADAPTER; + + updates[3].asset = AaveV3PolygonAssets.DAI_UNDERLYING; + updates[3].priceFeed = DAI_ADAPTER; + + updates[4].asset = AaveV3PolygonAssets.wstETH_UNDERLYING; + updates[4].priceFeed = wstETH_ADAPTER; + + updates[5].asset = AaveV3PolygonAssets.stMATIC_UNDERLYING; + updates[5].priceFeed = stMATIC_ADAPTER; + + updates[6].asset = AaveV3PolygonAssets.MaticX_UNDERLYING; + updates[6].priceFeed = MaticX_ADAPTER; + + return updates; + } +} diff --git a/src/interfaces/IPriceCapAdapter.sol b/src/interfaces/IPriceCapAdapter.sol new file mode 100644 index 0000000..5ae544f --- /dev/null +++ b/src/interfaces/IPriceCapAdapter.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; +import {ICLSynchronicityPriceAdapter} from 'cl-synchronicity-price-adapter/interfaces/ICLSynchronicityPriceAdapter.sol'; +import {IChainlinkAggregator} from 'cl-synchronicity-price-adapter/interfaces/IChainlinkAggregator.sol'; + +interface IPriceCapAdapter is ICLSynchronicityPriceAdapter { + /** + * @dev Emitted when the cap parameters are updated + * @param snapshotRatio the ratio at the time of snapshot + * @param snapshotTimestamp the timestamp at the time of snapshot + * @param maxRatioGrowthPerSecond max ratio growth per second + * @param maxYearlyRatioGrowthPercent max yearly ratio growth percent + **/ + event CapParametersUpdated( + uint256 snapshotRatio, + uint256 snapshotTimestamp, + uint256 maxRatioGrowthPerSecond, + uint16 maxYearlyRatioGrowthPercent + ); + + /** + * @notice Parameters to update price cap + * @param priceCapParams parameters to set price cap + */ + struct PriceCapUpdateParams { + uint104 snapshotRatio; + uint48 snapshotTimestamp; + uint16 maxYearlyRatioGrowthPercent; + } + + /** + * @notice Updates price cap parameters + * @param priceCapParams parameters to set price cap + */ + function setCapParameters(PriceCapUpdateParams memory priceCapParams) external; + + /** + * @notice Maximum percentage factor (100.00%) + */ + function PERCENTAGE_FACTOR() external view returns (uint256); + + /** + * @notice Minimal time while ratio should not overflow, in years + */ + function MINIMAL_RATIO_INCREASE_LIFETIME() external view returns (uint256); + + /** + * @notice Number of seconds per year (365 days) + */ + function SECONDS_PER_YEAR() external view returns (uint256); + + /** + * @notice Price feed for (BASE_ASSET / USD) pair + */ + function BASE_TO_USD_AGGREGATOR() external view returns (IChainlinkAggregator); + + /** + * @notice Ratio feed for (LST_ASSET / BASE_ASSET) pair + */ + function RATIO_PROVIDER() external view returns (address); + + /** + * @notice ACL manager contract + */ + function ACL_MANAGER() external view returns (IACLManager); + + /** + * @notice Number of decimals in the output of this price adapter + */ + function DECIMALS() external view returns (uint8); + + /** + * @notice Number of decimals for (lst asset / underlying asset) ratio + */ + function RATIO_DECIMALS() external view returns (uint8); + + /** + * @notice Minimum time (in seconds) that should have passed from the snapshot timestamp to the current block.timestamp + */ + function MINIMUM_SNAPSHOT_DELAY() external view returns (uint48); + + /** + * @notice Returns the current exchange ratio of lst to the underlying(base) asset + */ + function getRatio() external view returns (int256); + + /** + * @notice Returns the latest snapshot ratio + */ + function getSnapshotRatio() external view returns (uint256); + + /** + * @notice Returns the latest snapshot timestamp + */ + function getSnapshotTimestamp() external view returns (uint256); + + /** + * @notice Returns the max ratio growth per second + */ + function getMaxRatioGrowthPerSecond() external view returns (uint256); + + /** + * @notice Returns the max yearly ratio growth + */ + function getMaxYearlyGrowthRatePercent() external view returns (uint256); + + error ACLManagerIsZeroAddress(); + error SnapshotRatioIsZero(); + error SnapshotMayOverflowSoon(uint104 snapshotRatio, uint16 maxYearlyRatioGrowthPercent); + error InvalidRatioTimestamp(uint48 timestamp); + error CallerIsNotRiskOrPoolAdmin(); +} diff --git a/src/interfaces/IPriceCapAdapterStable.sol b/src/interfaces/IPriceCapAdapterStable.sol new file mode 100644 index 0000000..299bd9b --- /dev/null +++ b/src/interfaces/IPriceCapAdapterStable.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IACLManager} from 'aave-address-book/AaveV3.sol'; + +import {IChainlinkAggregator} from 'cl-synchronicity-price-adapter/interfaces/IChainlinkAggregator.sol'; +import {ICLSynchronicityPriceAdapter} from 'cl-synchronicity-price-adapter/interfaces/ICLSynchronicityPriceAdapter.sol'; + +interface IPriceCapAdapterStable is ICLSynchronicityPriceAdapter { + /** + * @dev Emitted when the price cap gets updated + * @param priceCap the new price cap + **/ + event PriceCapUpdated(int256 priceCap); + + /** + * @notice Price feed for (ASSET / USD) pair + */ + function ASSET_TO_USD_AGGREGATOR() external view returns (IChainlinkAggregator); + + /** + * @notice ACL manager contract + */ + function ACL_MANAGER() external view returns (IACLManager); + + /** + * @notice Updates price cap + * @param priceCap the new price cap + */ + function setPriceCap(int256 priceCap) external; + + error NegativePrice(int256 price); + error ACLManagerIsZeroAddress(); + error CallerIsNotRiskOrPoolAdmin(); +} diff --git a/src/interfaces/ISAvax.sol b/src/interfaces/ISAvax.sol new file mode 100644 index 0000000..5965972 --- /dev/null +++ b/src/interfaces/ISAvax.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice A simple version of the ISAvax interface allowing to get exchange ratio with AVAX +interface ISAvax { + /** + * @return The amount of AVAX that corresponds to `shareAmount` token shares. + */ + function getPooledAvaxByShares(uint256 shareAmount) external view returns (uint256); +} diff --git a/src/lib/MissingAssetsMainnet.sol b/src/lib/MissingAssetsMainnet.sol new file mode 100644 index 0000000..529f5b7 --- /dev/null +++ b/src/lib/MissingAssetsMainnet.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library MissingAssetsMainnet { + address constant public RETH = 0xae78736Cd615f374D3085123A210448E74Fc6393; + address constant public STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; +} diff --git a/tests/BaseTest.sol b/tests/BaseTest.sol new file mode 100644 index 0000000..b10d7e8 --- /dev/null +++ b/tests/BaseTest.sol @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {IACLManager, BasicIACLManager} from 'aave-address-book/AaveV3.sol'; +import {IPriceCapAdapter, ICLSynchronicityPriceAdapter} from '../src/interfaces/IPriceCapAdapter.sol'; + +abstract contract BaseTest is Test { + ICLSynchronicityPriceAdapter public immutable NOT_CAPPED_ADAPTER; + + constructor(address notCappedAdapter) { + NOT_CAPPED_ADAPTER = ICLSynchronicityPriceAdapter(notCappedAdapter); + } + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public virtual returns (IPriceCapAdapter); + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + uint104 snapshotRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public virtual returns (IPriceCapAdapter) { + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams; + priceCapParams.snapshotRatio = snapshotRatio; + priceCapParams.snapshotTimestamp = snapshotTimestamp; + priceCapParams.maxYearlyRatioGrowthPercent = maxYearlyRatioGrowthPercent; + + return + createAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public virtual returns (IPriceCapAdapter) { + return + createAdapterSimple( + minimumSnapshotDelay, + getCurrentRatio(), + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public virtual returns (IPriceCapAdapter); + + function setCapParameters( + IPriceCapAdapter adapter, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public { + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams; + priceCapParams.snapshotRatio = currentRatio; + priceCapParams.snapshotTimestamp = snapshotTimestamp; + priceCapParams.maxYearlyRatioGrowthPercent = maxYearlyRatioGrowthPercent; + adapter.setCapParameters(priceCapParams); + } + + function getCurrentRatio() public view virtual returns (uint104); + + function deploySimpleAndSetParams( + uint48 minimumSnapshotDelay, + uint16 maxYearlyRatioGrowthPercentInitial, + uint16 maxYearlyRatioGrowthPercentUpdated + ) public { + IPriceCapAdapter adapter = createAdapterSimple( + minimumSnapshotDelay, + uint48(block.timestamp) - minimumSnapshotDelay, + maxYearlyRatioGrowthPercentInitial + ); + + skip(1); + + vm.mockCall( + address(adapter.ACL_MANAGER()), + abi.encodeWithSelector(BasicIACLManager.isRiskAdmin.selector), + abi.encode(true) + ); + setCapParameters( + adapter, + uint104(adapter.getSnapshotRatio()) + 1, + uint48(block.timestamp) - minimumSnapshotDelay, + maxYearlyRatioGrowthPercentUpdated + ); + } + + function test_constructor( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint104 snapshotRatio, + uint16 minimumSnapshotDelay, + uint8 decimals, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public { + vm.assume(baseAggregatorAddress != 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f); + vm.assume(address(aclManager) != address(0) && baseAggregatorAddress != address(0)); + vm.assume(snapshotRatio > 0); + vm.assume(snapshotTimestamp > 0 && snapshotTimestamp <= block.timestamp - minimumSnapshotDelay); + + uint256 maxRatioGrowthInMinimalLifetime = ((uint256(snapshotRatio) * + maxYearlyRatioGrowthPercent) / + 100_00 / + 365 days) * + 3 * + 365 days; + vm.assume(snapshotRatio + maxRatioGrowthInMinimalLifetime <= type(uint104).max); + + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams; + priceCapParams.snapshotRatio = snapshotRatio; + priceCapParams.snapshotTimestamp = snapshotTimestamp; + priceCapParams.maxYearlyRatioGrowthPercent = maxYearlyRatioGrowthPercent; + + vm.mockCall( + baseAggregatorAddress, + abi.encodeWithSelector(ICLSynchronicityPriceAdapter.decimals.selector), + abi.encode(decimals) + ); + IPriceCapAdapter adapter = createAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + assertEq(address(adapter.ACL_MANAGER()), address(aclManager), 'aclManager not set properly'); + assertEq( + address(adapter.BASE_TO_USD_AGGREGATOR()), + baseAggregatorAddress, + 'baseAggregatorAddress not set properly' + ); + assertEq( + adapter.RATIO_PROVIDER(), + ratioProviderAddress, + 'ratioProviderAddress not set properly' + ); + assertEq(adapter.getSnapshotRatio(), snapshotRatio, 'snapshotRatio not set properly'); + assertEq(adapter.description(), pairDescription, 'pairDescription not set properly'); + assertEq(adapter.decimals(), decimals, 'decimals not set properly'); + assertEq( + adapter.getSnapshotTimestamp(), + snapshotTimestamp, + 'snapshotTimestamp not set properly' + ); + assertEq( + adapter.getMaxRatioGrowthPerSecond(), + (uint256(snapshotRatio) * maxYearlyRatioGrowthPercent) / 100_00 / 365 days, + 'getMaxRatioGrowthPerSecond not set properly' + ); + assertEq( + adapter.getMaxYearlyGrowthRatePercent(), + maxYearlyRatioGrowthPercent, + 'maxYearlyRatioGrowthPercent not set properly' + ); + } + + function test_setParams( + uint16 minimumSnapshotDelay, + uint16 maxYearlyRatioGrowthPercentInitial, + uint16 maxYearlyRatioGrowthPercentUpdated + ) public { + vm.assume(block.timestamp > minimumSnapshotDelay); + + IPriceCapAdapter adapter = createAdapterSimple( + minimumSnapshotDelay, + uint48(block.timestamp) - minimumSnapshotDelay, + maxYearlyRatioGrowthPercentInitial + ); + + skip(1); + + vm.mockCall( + address(adapter.ACL_MANAGER()), + abi.encodeWithSelector(BasicIACLManager.isRiskAdmin.selector), + abi.encode(true) + ); + setCapParameters( + adapter, + uint104(adapter.getSnapshotRatio()) + 1, + uint48(block.timestamp) - minimumSnapshotDelay, + maxYearlyRatioGrowthPercentUpdated + ); + } + + function test_revert_constructor_timestamp_gt_aligning_interval( + uint16 minimumSnapshotDelay, + uint16 maxYearlyRatioGrowthPercentInitial, + uint48 timestamp + ) public { + vm.assume(timestamp > block.timestamp - minimumSnapshotDelay); + + uint104 currentRatio = getCurrentRatio(); + vm.expectRevert( + abi.encodeWithSelector(IPriceCapAdapter.InvalidRatioTimestamp.selector, timestamp) + ); + createAdapterSimple( + minimumSnapshotDelay, + currentRatio, + timestamp, + maxYearlyRatioGrowthPercentInitial + ); + } + + function test_revert_setParams_timestamp_lt_existing_timestamp( + uint16 minimumSnapshotDelay, + uint48 timestamp, + uint48 timestampUpdate + ) public { + vm.assume(block.timestamp > minimumSnapshotDelay); + vm.assume(timestamp <= block.timestamp - minimumSnapshotDelay); + vm.assume(timestampUpdate < timestamp); + + IPriceCapAdapter adapter = createAdapterSimple(minimumSnapshotDelay, 1, timestamp, 1); + + vm.mockCall( + address(adapter.ACL_MANAGER()), + abi.encodeWithSelector(BasicIACLManager.isRiskAdmin.selector), + abi.encode(true) + ); + vm.expectRevert( + abi.encodeWithSelector(IPriceCapAdapter.InvalidRatioTimestamp.selector, timestampUpdate) + ); + setCapParameters(adapter, 1, timestampUpdate, 1); + } + + function test_revert_constructor_current_ratio_is_0( + uint16 maxYearlyRatioGrowthPercentInitial, + uint48 timestamp + ) public { + vm.assume(timestamp < block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(IPriceCapAdapter.SnapshotRatioIsZero.selector)); + createAdapterSimple(0, 0, timestamp, maxYearlyRatioGrowthPercentInitial); + } + + function test_revert_updateParams_by_not_risk_or_pool_admin() public { + IPriceCapAdapter adapter = createAdapterSimple(1, 1, 1); + address aclManager = address(adapter.ACL_MANAGER()); + + vm.mockCall( + aclManager, + abi.encodeWithSelector(BasicIACLManager.isPoolAdmin.selector), + abi.encode(false) + ); + vm.mockCall( + aclManager, + abi.encodeWithSelector(BasicIACLManager.isRiskAdmin.selector), + abi.encode(false) + ); + vm.expectRevert(IPriceCapAdapter.CallerIsNotRiskOrPoolAdmin.selector); + setCapParameters(adapter, 1, 1, 1); + } + + function test_latestAnswer(uint16 maxYearlyRatioGrowthPercent) public virtual { + IPriceCapAdapter adapter = createAdapterSimple( + 0, + uint40(block.timestamp), + maxYearlyRatioGrowthPercent + ); + + int256 price = adapter.latestAnswer(); + int256 priceOfNotCappedAdapter = NOT_CAPPED_ADAPTER.latestAnswer(); + + assertEq( + price, + priceOfNotCappedAdapter, + 'uncapped price is not equal to the existing adapter price' + ); + } +} diff --git a/tests/CbETHPriceCapAdapterTest.t.sol b/tests/CbETHPriceCapAdapterTest.t.sol new file mode 100644 index 0000000..11ae07b --- /dev/null +++ b/tests/CbETHPriceCapAdapterTest.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseTest.sol'; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {BaseAggregatorsMainnet} from 'cl-synchronicity-price-adapter/lib/BaseAggregators.sol'; + +import {CbETHPriceCapAdapter, ICbEthRateProvider} from '../src/contracts/CbETHPriceCapAdapter.sol'; +import {ICLSynchronicityPriceAdapter} from '../src/interfaces/IPriceCapAdapter.sol'; + +contract CbETHPriceCapAdapterTest is BaseTest { + constructor() BaseTest(AaveV3EthereumAssets.cbETH_ORACLE) {} + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public override returns (IPriceCapAdapter) { + return + new CbETHPriceCapAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public override returns (IPriceCapAdapter) { + return + createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + AaveV3EthereumAssets.cbETH_UNDERLYING, + 'cbETH / ETH / USD', + minimumSnapshotDelay, + currentRatio, + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function getCurrentRatio() public view override returns (uint104) { + return uint104(ICbEthRateProvider(AaveV3EthereumAssets.cbETH_UNDERLYING).exchangeRate()); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 18961286); + } + + function test_latestAnswer(uint16 maxYearlyRatioGrowthPercent) public override { + IPriceCapAdapter adapter = createAdapterSimple( + 0, + uint40(block.timestamp), + maxYearlyRatioGrowthPercent + ); + + int256 price = adapter.latestAnswer(); + + // here we have a very specific case, because we replace secondary-market based CL feed of exchange rate + // with the primary cbETH based feed + uint256 cbEthRate = getCurrentRatio(); + vm.mockCall( + BaseAggregatorsMainnet.CBETH_ETH_AGGREGATOR, + abi.encodeWithSelector(ICLSynchronicityPriceAdapter.latestAnswer.selector), + abi.encode(int256(cbEthRate)) + ); + int256 priceOfNotCappedAdapter = NOT_CAPPED_ADAPTER.latestAnswer(); + + assertEq( + price, + priceOfNotCappedAdapter, + 'uncapped price is not equal to the existing adapter price' + ); + } + + function test_cappedLatestAnswer() public { + IPriceCapAdapter adapter = createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + AaveV3EthereumAssets.cbETH_UNDERLYING, + 'cbETH / ETH / USD', + 7 days, + 1059523963000000000, + 1703743921, + 2_00 + ); + + int256 price = adapter.latestAnswer(); + + assertApproxEqAbs( + uint256(price), + 235982310000, // max growth 2% + 100000000 + ); + } +} diff --git a/tests/MaticPriceCapAdapterTest.t.sol b/tests/MaticPriceCapAdapterTest.t.sol new file mode 100644 index 0000000..f8ed1fe --- /dev/null +++ b/tests/MaticPriceCapAdapterTest.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseTest.sol'; + +import {AaveV3Polygon, AaveV3PolygonAssets} from 'aave-address-book/AaveV3Polygon.sol'; +import {BaseAggregatorsPolygon} from 'cl-synchronicity-price-adapter/lib/BaseAggregators.sol'; + +import {MaticPriceCapAdapter, IMaticRateProvider} from '../src/contracts/MaticPriceCapAdapter.sol'; + +abstract contract BaseMaticPriceCapAdapterTest is BaseTest { + address public immutable RATE_PROVIDER; + string internal _pairName; + + constructor( + address notCappedAdapter, + address rateProvider, + string memory pairName + ) BaseTest(notCappedAdapter) { + RATE_PROVIDER = rateProvider; + _pairName = pairName; + } + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public override returns (IPriceCapAdapter) { + return + new MaticPriceCapAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public override returns (IPriceCapAdapter) { + return + createAdapter( + AaveV3Polygon.ACL_MANAGER, + BaseAggregatorsPolygon.MATIC_USD_AGGREGATOR, + RATE_PROVIDER, + _pairName, + minimumSnapshotDelay, + currentRatio, + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function getCurrentRatio() public view override returns (uint104) { + return uint104(IMaticRateProvider(RATE_PROVIDER).getRate()); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('polygon'), 52496345); + } +} + +contract MaticXPriceCapAdapterTest is BaseMaticPriceCapAdapterTest { + constructor() + BaseMaticPriceCapAdapterTest( + AaveV3PolygonAssets.MaticX_ORACLE, + BaseAggregatorsPolygon.MATICX_RATE_PROVIDER, + 'MaticX / Matic / USD' + ) + {} +} + +contract StMaticPriceCapAdapterTest is BaseMaticPriceCapAdapterTest { + constructor() + BaseMaticPriceCapAdapterTest( + AaveV3PolygonAssets.stMATIC_ORACLE, + BaseAggregatorsPolygon.STMATIC_RATE_PROVIDER, + 'stMATIC / Matic / USD' + ) + {} +} diff --git a/tests/RETHPriceCapAdapterTest.t.sol b/tests/RETHPriceCapAdapterTest.t.sol new file mode 100644 index 0000000..94ca95a --- /dev/null +++ b/tests/RETHPriceCapAdapterTest.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseTest.sol'; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {BaseAggregatorsMainnet} from 'cl-synchronicity-price-adapter/lib/BaseAggregators.sol'; + +import {RETHPriceCapAdapter, IrETH} from '../src/contracts/RETHPriceCapAdapter.sol'; +import {MissingAssetsMainnet} from '../src/lib/MissingAssetsMainnet.sol'; + +contract RETHPriceCapAdapterTest is BaseTest { + constructor() BaseTest(AaveV3EthereumAssets.rETH_ORACLE) {} + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public override returns (IPriceCapAdapter) { + return + new RETHPriceCapAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public override returns (IPriceCapAdapter) { + return + createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + AaveV3EthereumAssets.rETH_UNDERLYING, + 'rETH / ETH / USD', + minimumSnapshotDelay, + currentRatio, + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function getCurrentRatio() public view override returns (uint104) { + return uint104(IrETH(MissingAssetsMainnet.RETH).getExchangeRate()); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 18961286); + } + + function test_cappedLatestAnswer() public { + IPriceCapAdapter adapter = createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + AaveV3EthereumAssets.rETH_UNDERLYING, + 'rETH / ETH / USD', + 7 days, + 1093801647000000000, + 1703743921, + 2_00 + ); + + int256 price = adapter.latestAnswer(); + + assertApproxEqAbs( + uint256(price), + 243616800000, // max growth 2% + 100000000 + ); + } +} diff --git a/tests/SAvaxPriceCapAdapterTest.t.sol b/tests/SAvaxPriceCapAdapterTest.t.sol new file mode 100644 index 0000000..355508f --- /dev/null +++ b/tests/SAvaxPriceCapAdapterTest.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseTest.sol'; + +import {AaveV3Avalanche, AaveV3AvalancheAssets} from 'aave-address-book/AaveV3Avalanche.sol'; +import {BaseAggregatorsMainnet} from 'cl-synchronicity-price-adapter/lib/BaseAggregators.sol'; + +import {SAvaxPriceCapAdapter, ISAvax} from '../src/contracts/SAvaxPriceCapAdapter.sol'; + +contract SAvaxPriceCapAdapterTest is BaseTest { + constructor() BaseTest(AaveV3AvalancheAssets.sAVAX_ORACLE) {} + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public override returns (IPriceCapAdapter) { + return + new SAvaxPriceCapAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public override returns (IPriceCapAdapter) { + return + createAdapter( + AaveV3Avalanche.ACL_MANAGER, + AaveV3AvalancheAssets.WAVAX_ORACLE, + AaveV3AvalancheAssets.sAVAX_UNDERLYING, + 'sAvax / Avax / USD', + minimumSnapshotDelay, + currentRatio, + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function getCurrentRatio() public view override returns (uint104) { + return uint104(ISAvax(AaveV3AvalancheAssets.sAVAX_UNDERLYING).getPooledAvaxByShares(10 ** 18)); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('avalanche'), 40555293); + } +} diff --git a/tests/SDAIGnosisPriceCapAdapterTest.t.sol b/tests/SDAIGnosisPriceCapAdapterTest.t.sol new file mode 100644 index 0000000..484fc11 --- /dev/null +++ b/tests/SDAIGnosisPriceCapAdapterTest.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseTest.sol'; + +import {AaveV3Gnosis, AaveV3GnosisAssets} from 'aave-address-book/AaveV3Gnosis.sol'; +import {BaseAggregatorsGnosis} from 'cl-synchronicity-price-adapter/lib/BaseAggregators.sol'; +import {IERC4626} from 'forge-std/interfaces/IERC4626.sol'; + +import {SDAIGnosisPriceCapAdapter} from '../src/contracts/SDAIGnosisPriceCapAdapter.sol'; + +contract SDAIGnosisPriceCapAdapterTest is BaseTest { + constructor() BaseTest(AaveV3GnosisAssets.sDAI_ORACLE) {} + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public override returns (IPriceCapAdapter) { + return + new SDAIGnosisPriceCapAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public override returns (IPriceCapAdapter) { + return + createAdapter( + AaveV3Gnosis.ACL_MANAGER, + BaseAggregatorsGnosis.DAI_USD_AGGREGATOR, + AaveV3GnosisAssets.sDAI_UNDERLYING, + 'sDAIGno / DAI / USD', + minimumSnapshotDelay, + currentRatio, + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function getCurrentRatio() public view override returns (uint104) { + return uint104(IERC4626(AaveV3GnosisAssets.sDAI_UNDERLYING).convertToAssets(10 ** 18)); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('gnosis'), 32019351); + } +} diff --git a/tests/SDAIPriceCapAdapterTest.t.sol b/tests/SDAIPriceCapAdapterTest.t.sol new file mode 100644 index 0000000..a2cd3e7 --- /dev/null +++ b/tests/SDAIPriceCapAdapterTest.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseTest.sol'; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {BaseAggregatorsMainnet} from 'cl-synchronicity-price-adapter/lib/BaseAggregators.sol'; + +import {SDAIPriceCapAdapter, IPot} from '../src/contracts/SDAIPriceCapAdapter.sol'; + +contract SDAIPriceCapAdapterTest is BaseTest { + constructor() BaseTest(AaveV3EthereumAssets.sDAI_ORACLE) {} + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public override returns (IPriceCapAdapter) { + return + new SDAIPriceCapAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public override returns (IPriceCapAdapter) { + return + createAdapter( + AaveV3Ethereum.ACL_MANAGER, + BaseAggregatorsMainnet.DAI_USD_AGGREGATOR, + BaseAggregatorsMainnet.SDAI_POT, + 'sDAI / DAI / USD', + minimumSnapshotDelay, + currentRatio, + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function getCurrentRatio() public view override returns (uint104) { + return uint104(IPot(BaseAggregatorsMainnet.SDAI_POT).chi()); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 18961286); + } + + function test_cappedLatestAnswer() public { + IPriceCapAdapter adapter = createAdapter( + AaveV3Ethereum.ACL_MANAGER, + BaseAggregatorsMainnet.DAI_USD_AGGREGATOR, + BaseAggregatorsMainnet.SDAI_POT, + 'sDAI / DAI / USD', + 7 days, + 1048947230000000000000000000, + 1703743921, + 1_00 + ); + + int256 price = adapter.latestAnswer(); + + assertApproxEqAbs( + uint256(price), + 104911324, // max growth 2% + 100000000 + ); + } +} diff --git a/tests/WstETHPriceCapAdapterTest.t.sol b/tests/WstETHPriceCapAdapterTest.t.sol new file mode 100644 index 0000000..b2a1bfa --- /dev/null +++ b/tests/WstETHPriceCapAdapterTest.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseTest.sol'; + +import {AaveV3Ethereum, AaveV3EthereumAssets, IACLManager} from 'aave-address-book/AaveV3Ethereum.sol'; +import {BaseAggregatorsMainnet} from 'cl-synchronicity-price-adapter/lib/BaseAggregators.sol'; + +import {WstETHPriceCapAdapter, IStETH} from '../src/contracts/WstETHPriceCapAdapter.sol'; +import {IPriceCapAdapter, ICLSynchronicityPriceAdapter} from '../src/interfaces/IPriceCapAdapter.sol'; +import {MissingAssetsMainnet} from '../src/lib/MissingAssetsMainnet.sol'; + +contract WstETHPriceCapAdapterTest is BaseTest { + constructor() BaseTest(AaveV3EthereumAssets.wstETH_ORACLE) {} + + function createAdapter( + IACLManager aclManager, + address baseAggregatorAddress, + address ratioProviderAddress, + string memory pairDescription, + uint48 minimumSnapshotDelay, + IPriceCapAdapter.PriceCapUpdateParams memory priceCapParams + ) public override returns (IPriceCapAdapter) { + return + new WstETHPriceCapAdapter( + aclManager, + baseAggregatorAddress, + ratioProviderAddress, + pairDescription, + minimumSnapshotDelay, + priceCapParams + ); + } + + function createAdapterSimple( + uint48 minimumSnapshotDelay, + uint104 currentRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public override returns (IPriceCapAdapter) { + return + createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + MissingAssetsMainnet.STETH, + 'wstETH/stETH/USD', + minimumSnapshotDelay, + currentRatio, + snapshotTimestamp, + maxYearlyRatioGrowthPercent + ); + } + + function getCurrentRatio() public view override returns (uint104) { + return uint104(uint256(IStETH(MissingAssetsMainnet.STETH).getPooledEthByShares(10 ** 18))); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 18961286); + } + + // TODO: test that setParams func sets params as expected + + function test_cappedLatestAnswer() public { + IPriceCapAdapter adapter = createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + MissingAssetsMainnet.STETH, + 'wstETH/stETH/USD', + 7 days, + 1151642949000000000, + 1703743921, + 2_00 + ); + + int256 price = adapter.latestAnswer(); + + assertApproxEqAbs( + uint256(price), + 256499500000, // max growth 2% + 100000000 + ); + } + + function test_updateParameters_cappedLatestAnswer() public { + IPriceCapAdapter adapter = createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + MissingAssetsMainnet.STETH, + 'wstETH/stETH/USD', + 7 days, + 1151642949000000000, + 1703743921, + 2_00 + ); + + int256 price = adapter.latestAnswer(); + + assertApproxEqAbs( + uint256(price), + 256499500000, // max growth 2% + 100000000 + ); + + vm.prank(AaveV3Ethereum.CAPS_PLUS_RISK_STEWARD); + setCapParameters(adapter, 1151642955000000000, 1703743931, 20_00); + + price = adapter.latestAnswer(); + + assertApproxEqAbs( + uint256(price), + 256617830000, // value for selected block + 100000000 + ); + } + + function test_revert_updateParameters_notRiskAdmin( + uint104 snapshotRatio, + uint48 snapshotTimestamp, + uint16 maxYearlyRatioGrowthPercent + ) public { + IPriceCapAdapter adapter = createAdapter( + AaveV3Ethereum.ACL_MANAGER, + AaveV3EthereumAssets.WETH_ORACLE, + MissingAssetsMainnet.STETH, + 'wstETH/stETH/USD', + 7 days, + 1151642949000000000, + 1703743921, + 2_00 + ); + + // TODO: fuzzing? + vm.expectRevert(IPriceCapAdapter.CallerIsNotRiskOrPoolAdmin.selector); + setCapParameters(adapter, snapshotRatio, snapshotTimestamp, maxYearlyRatioGrowthPercent); + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..37a985d --- /dev/null +++ b/yarn.lock @@ -0,0 +1,53 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@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" + +antlr4ts@^0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +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.16.0" + semver "^7.3.8" + solidity-comments-extractor "^0.0.7" + +prettier@2.8.7: + version "2.8.7" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" + integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== + +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +solidity-comments-extractor@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" + integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==