From 840bcf3f664629b2d344e30daff0c3dbedb2698b Mon Sep 17 00:00:00 2001 From: Bartek Rutkowski Date: Fri, 21 Jun 2024 15:23:12 +0200 Subject: [PATCH] [SC-455] June 27 Spell (#46) * feat: bootstrap spell * fix: WIP debugging * refactor: cleanup * refactor: remove logs * refactor: add minor refactorings * refactor: remove redundant spaces * feat: add votes, remove pranks, add diff * refactor: remove redundant _bpsToRay * fix: remove unused imports * test: add USDC.e to deal2 * fix: fix USDC.e flashloan test * fix: fix rounding error * fix: remove unused import * fix: remove redundant spaces * fix: restrict function purity * feat: deploy --- .../20240613/SparkEthereum_20240613.sol | 0 .../20240613/SparkEthereum_20240613.t.sol | 0 ...B648A5Ee794902342038FF3aDAB66BE987-post.md | 5 + ...07c5E3FD1CF5A72Cb6F698f915860607e0-post.md | 145 ++++++++++++++++++ src/CommonTestBase.sol | 9 +- src/ProtocolV3TestBase.sol | 9 +- .../20240627/SparkEthereum_20240627.sol | 55 +++++++ .../20240627/SparkEthereum_20240627.t.sol | 90 +++++++++++ .../20240627/SparkGnosis_20240627.sol | 101 ++++++++++++ .../20240627/SparkGnosis_20240627.t.sol | 143 +++++++++++++++++ 10 files changed, 552 insertions(+), 5 deletions(-) rename {src/proposals => archive}/20240613/SparkEthereum_20240613.sol (100%) rename {src/proposals => archive}/20240613/SparkEthereum_20240613.t.sol (100%) create mode 100644 diffs/20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-pre_20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-post.md create mode 100644 diffs/20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-pre_20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-post.md create mode 100644 src/proposals/20240627/SparkEthereum_20240627.sol create mode 100644 src/proposals/20240627/SparkEthereum_20240627.t.sol create mode 100644 src/proposals/20240627/SparkGnosis_20240627.sol create mode 100644 src/proposals/20240627/SparkGnosis_20240627.t.sol diff --git a/src/proposals/20240613/SparkEthereum_20240613.sol b/archive/20240613/SparkEthereum_20240613.sol similarity index 100% rename from src/proposals/20240613/SparkEthereum_20240613.sol rename to archive/20240613/SparkEthereum_20240613.sol diff --git a/src/proposals/20240613/SparkEthereum_20240613.t.sol b/archive/20240613/SparkEthereum_20240613.t.sol similarity index 100% rename from src/proposals/20240613/SparkEthereum_20240613.t.sol rename to archive/20240613/SparkEthereum_20240613.t.sol diff --git a/diffs/20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-pre_20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-post.md b/diffs/20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-pre_20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-post.md new file mode 100644 index 00000000..c15d3e2b --- /dev/null +++ b/diffs/20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-pre_20240627-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-post.md @@ -0,0 +1,5 @@ +## Raw diff + +```json +{} +``` \ No newline at end of file diff --git a/diffs/20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-pre_20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-post.md b/diffs/20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-pre_20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-post.md new file mode 100644 index 00000000..80accfe7 --- /dev/null +++ b/diffs/20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-pre_20240627-Gnosis-0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0-post.md @@ -0,0 +1,145 @@ +## Reserve changes + +### Reserves added + +#### USDC.e ([0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0](https://gnosisscan.io/address/0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0)) + +| description | value | +| --- | --- | +| decimals | 6 | +| isActive | true | +| isFrozen | false | +| supplyCap | 10,000,000 USDC.e | +| borrowCap | 8,000,000 USDC.e | +| debtCeiling | 0 $ | +| isSiloed | false | +| isFlashloanable | true | +| eModeCategory | 0 | +| oracle | [0x6FC2871B6d9A94866B7260896257Fd5b50c09900](https://gnosisscan.io/address/0x6FC2871B6d9A94866B7260896257Fd5b50c09900) | +| oracleDecimals | 8 | +| oracleLatestAnswer | 1 | +| usageAsCollateralEnabled | false | +| ltv | 0 % | +| liquidationThreshold | 0 % | +| liquidationBonus | 0 % | +| liquidationProtocolFee | 0 % | +| reserveFactor | 10 % | +| aToken | [0xA34DB0ee8F84C4B90ed268dF5aBbe7Dcd3c277ec](https://gnosisscan.io/address/0xA34DB0ee8F84C4B90ed268dF5aBbe7Dcd3c277ec) | +| aTokenImpl | [0x856900aa78e856a5df1a2665eE3a66b2487cD68f](https://gnosisscan.io/address/0x856900aa78e856a5df1a2665eE3a66b2487cD68f) | +| variableDebtToken | [0x397b97b572281d0b3e3513BD4A7B38050a75962b](https://gnosisscan.io/address/0x397b97b572281d0b3e3513BD4A7B38050a75962b) | +| variableDebtTokenImpl | [0x0ee554F6A1f7a4Cb4f82D4C124DdC2AD3E37fde1](https://gnosisscan.io/address/0x0ee554F6A1f7a4Cb4f82D4C124DdC2AD3E37fde1) | +| stableDebtToken | [0xC5dfde524371F9424c81F453260B2CCd24936c15](https://gnosisscan.io/address/0xC5dfde524371F9424c81F453260B2CCd24936c15) | +| stableDebtTokenImpl | [0x4370D3b6C9588E02ce9D22e684387859c7Ff5b34](https://gnosisscan.io/address/0x4370D3b6C9588E02ce9D22e684387859c7Ff5b34) | +| borrowingEnabled | true | +| stableBorrowRateEnabled | false | +| isBorrowableInIsolation | true | +| interestRateStrategy | [0xe66F24175A204E7286F0609CC594667D343E7aAE](https://gnosisscan.io/address/0xe66F24175A204E7286F0609CC594667D343E7aAE) | +| aTokenName | Spark USDC.e | +| aTokenSymbol | spUSDC.e | +| isPaused | false | +| stableDebtTokenName | Spark Stable Debt USDC.e | +| stableDebtTokenSymbol | stableDebtUSDC.e | +| variableDebtTokenName | Spark Variable Debt USDC.e | +| variableDebtTokenSymbol | variableDebtUSDC.e | +| optimalUsageRatio | 95 % | +| maxExcessUsageRatio | 5 % | +| baseVariableBorrowRate | 0 % | +| variableRateSlope1 | 9 % | +| variableRateSlope2 | 15 % | +| baseStableBorrowRate | 9 % | +| stableRateSlope1 | 0 % | +| stableRateSlope2 | 0 % | +| optimalStableToTotalDebtRatio | 0 % | +| maxExcessStableToTotalDebtRatio | 100 % | + + +### Reserves altered + +#### USDC ([0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83](https://gnosisscan.io/address/0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83)) + +| description | value before | value after | +| --- | --- | --- | +| borrowCap | 8,000,000 USDC | 1,000,000 USDC | +| interestRateStrategy | [0xe66F24175A204E7286F0609CC594667D343E7aAE](https://gnosisscan.io/address/0xe66F24175A204E7286F0609CC594667D343E7aAE) | [0x410CB8b77129AeB28fE66F73deef8AC91A36c9AB](https://gnosisscan.io/address/0x410CB8b77129AeB28fE66F73deef8AC91A36c9AB) | +| optimalUsageRatio | 95 % | 80 % | +| maxExcessUsageRatio | 5 % | 20 % | +| variableRateSlope2 | 15 % | 50 % | + + +## Raw diff + +```json +{ + "reserves": { + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83": { + "borrowCap": { + "from": 8000000, + "to": 1000000 + }, + "interestRateStrategy": { + "from": "0xe66F24175A204E7286F0609CC594667D343E7aAE", + "to": "0x410CB8b77129AeB28fE66F73deef8AC91A36c9AB" + } + }, + "0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0": { + "from": null, + "to": { + "aToken": "0xA34DB0ee8F84C4B90ed268dF5aBbe7Dcd3c277ec", + "aTokenImpl": "0x856900aa78e856a5df1a2665eE3a66b2487cD68f", + "aTokenName": "Spark USDC.e", + "aTokenSymbol": "spUSDC.e", + "borrowCap": 8000000, + "borrowingEnabled": true, + "debtCeiling": 0, + "decimals": 6, + "eModeCategory": 0, + "interestRateStrategy": "0xe66F24175A204E7286F0609CC594667D343E7aAE", + "isActive": true, + "isBorrowableInIsolation": true, + "isFlashloanable": true, + "isFrozen": false, + "isPaused": false, + "isSiloed": false, + "liquidationBonus": 0, + "liquidationProtocolFee": 0, + "liquidationThreshold": 0, + "ltv": 0, + "oracle": "0x6FC2871B6d9A94866B7260896257Fd5b50c09900", + "oracleDecimals": 8, + "oracleLatestAnswer": 100000000, + "reserveFactor": 1000, + "stableBorrowRateEnabled": false, + "stableDebtToken": "0xC5dfde524371F9424c81F453260B2CCd24936c15", + "stableDebtTokenImpl": "0x4370D3b6C9588E02ce9D22e684387859c7Ff5b34", + "stableDebtTokenName": "Spark Stable Debt USDC.e", + "stableDebtTokenSymbol": "stableDebtUSDC.e", + "supplyCap": 10000000, + "symbol": "USDC.e", + "underlying": "0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0", + "usageAsCollateralEnabled": false, + "variableDebtToken": "0x397b97b572281d0b3e3513BD4A7B38050a75962b", + "variableDebtTokenImpl": "0x0ee554F6A1f7a4Cb4f82D4C124DdC2AD3E37fde1", + "variableDebtTokenName": "Spark Variable Debt USDC.e", + "variableDebtTokenSymbol": "variableDebtUSDC.e" + } + } + }, + "strategies": { + "0x410CB8b77129AeB28fE66F73deef8AC91A36c9AB": { + "from": null, + "to": { + "baseStableBorrowRate": "90000000000000000000000000", + "baseVariableBorrowRate": 0, + "maxExcessStableToTotalDebtRatio": "1000000000000000000000000000", + "maxExcessUsageRatio": "200000000000000000000000000", + "optimalStableToTotalDebtRatio": 0, + "optimalUsageRatio": "800000000000000000000000000", + "stableRateSlope1": 0, + "stableRateSlope2": 0, + "variableRateSlope1": "90000000000000000000000000", + "variableRateSlope2": "500000000000000000000000000" + } + } + } +} +``` \ No newline at end of file diff --git a/src/CommonTestBase.sol b/src/CommonTestBase.sol index e57e9a2e..97d9aafc 100644 --- a/src/CommonTestBase.sol +++ b/src/CommonTestBase.sol @@ -26,7 +26,8 @@ contract CommonTestBase is Test { address public constant USDC_MAINNET = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - address public constant EURE_GNOSIS = 0xcB444e90D8198415266c6a2724b7900fb12FC56E; + address public constant EURE_GNOSIS = 0xcB444e90D8198415266c6a2724b7900fb12FC56E; + address public constant USDCE_GNOSIS = 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0; /** * @notice deal doesn't support amounts stored in a script right now. @@ -51,6 +52,12 @@ contract CommonTestBase is Test { IERC20(asset).transfer(user, amount); return true; } + // USDC.e + if (asset == USDCE_GNOSIS) { + vm.prank(0xBA12222222228d8Ba445958a75a0704d566BF2C8); + IERC20(asset).transfer(user, amount); + return true; + } } return false; } diff --git a/src/ProtocolV3TestBase.sol b/src/ProtocolV3TestBase.sol index a5011a62..38d6eb1c 100644 --- a/src/ProtocolV3TestBase.sol +++ b/src/ProtocolV3TestBase.sol @@ -312,12 +312,13 @@ contract ProtocolV3TestBase is CommonTestBase { ReserveConfig memory borrowConfig, uint256 collateralAmount ) internal view returns (uint256) { + // Intentionally introducing a slight rounding error to not trigger the LTV edge case failure condition return collateralAmount * _getTokenPrice(pool, collateralConfig) - * collateralConfig.ltv - * (10 ** borrowConfig.decimals) / _getTokenPrice(pool, borrowConfig) + * (10 ** borrowConfig.decimals) / (10 ** collateralConfig.decimals) + * collateralConfig.ltv / 100_00; } @@ -560,8 +561,8 @@ contract ProtocolV3TestBase is CommonTestBase { address pool = abi.decode(params, (address)); assertEq(IERC20(asset).balanceOf(address(this)), amount, 'UNDERLYING_NOT_AMOUNT'); - // Temporary measure while USDC/EURe deal gets fixed, set the balance to amount + premium either way - uint256 dealAmount = asset == USDC_MAINNET || asset == EURE_GNOSIS ? premium : amount + premium; + // Temporary measure while USDC/EURe/USDC.e deal gets fixed, set the balance to amount + premium either way + uint256 dealAmount = asset == USDC_MAINNET || asset == EURE_GNOSIS || asset == USDCE_GNOSIS ? premium : amount + premium; deal2(asset, address(this), dealAmount); vm.startPrank(address(this)); diff --git a/src/proposals/20240627/SparkEthereum_20240627.sol b/src/proposals/20240627/SparkEthereum_20240627.sol new file mode 100644 index 00000000..117cbcd8 --- /dev/null +++ b/src/proposals/20240627/SparkEthereum_20240627.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; + +import { IMetaMorpho, MarketParams } from 'lib/metamorpho/src/interfaces/IMetaMorpho.sol'; +import { Gnosis } from 'lib/spark-address-registry/src/Gnosis.sol'; +import { XChainForwarders } from 'lib/xchain-helpers/src/XChainForwarders.sol'; + +import { SparkPayloadEthereum, Ethereum } from 'src/SparkPayloadEthereum.sol'; + +/** + * @title June 27, 2024 Spark Ethereum Proposal + * @notice Update Morpho supply caps, trigger Gnosis payload + * @author Phoenix Labs + * Forum: https://forum.makerdao.com/t/jun-12-2024-proposed-changes-to-sparklend-for-upcoming-spell/24489 + * Votes: https://vote.makerdao.com/polling/QmQv9zQR + * https://vote.makerdao.com/polling/QmU6KSGc + * https://vote.makerdao.com/polling/QmdQYTQe + */ +contract SparkEthereum_20240627 is SparkPayloadEthereum { + address public constant GNOSIS_PAYLOAD = 0xd5A8d293Ce8B31123C285d55d0232b3C31c4D217; + + function _postExecute() + internal override + { + // Morpho Vault Supply Cap Changes + IMetaMorpho(Ethereum.MORPHO_VAULT_DAI_1).submitCap( + MarketParams({ + loanToken: Ethereum.DAI, + collateralToken: Ethereum.SUSDE, + oracle: Ethereum.MORPHO_SUSDE_ORACLE, + irm: Ethereum.MORPHO_DEFAULT_IRM, + lltv: 0.86e18 + }), + 500_000_000e18 + ); + IMetaMorpho(Ethereum.MORPHO_VAULT_DAI_1).submitCap( + MarketParams({ + loanToken: Ethereum.DAI, + collateralToken: Ethereum.SUSDE, + oracle: Ethereum.MORPHO_SUSDE_ORACLE, + irm: Ethereum.MORPHO_DEFAULT_IRM, + lltv: 0.915e18 + }), + 200_000_000e18 + ); + + // Trigger Gnosis Payload + XChainForwarders.sendMessageGnosis( + Gnosis.AMB_EXECUTOR, + encodePayloadQueue(GNOSIS_PAYLOAD), + 4_000_000 + ); + } + +} diff --git a/src/proposals/20240627/SparkEthereum_20240627.t.sol b/src/proposals/20240627/SparkEthereum_20240627.t.sol new file mode 100644 index 00000000..e6598da9 --- /dev/null +++ b/src/proposals/20240627/SparkEthereum_20240627.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; + +import '../../SparkTestBase.sol'; + +import { IL2BridgeExecutor } from 'spark-gov-relay/interfaces/IL2BridgeExecutor.sol'; + +import { Domain, GnosisDomain } from 'xchain-helpers/testing/GnosisDomain.sol'; + +import { SparkGnosis_20240627 } from './SparkGnosis_20240627.sol'; + +contract SparkEthereum_20240627Test is SparkEthereumTestBase { + + address public constant USDCE = 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0; + + Domain mainnet; + GnosisDomain gnosis; + + constructor() { + id = '20240627'; + } + + function setUp() public { + mainnet = new Domain(getChain('mainnet')); + gnosis = new GnosisDomain(getChain('gnosis_chain'), mainnet); + + mainnet.rollFork(20134486); // June 20, 2024 + gnosis.rollFork(34563897); // June 20, 2024 + + payload = 0xc96420Dbe9568e2a65DD57daAD069FDEd37265fa; + + loadPoolContext(poolAddressesProviderRegistry.getAddressesProvidersList()[0]); + } + + function testGnosisSpellExecution() public { + executePayload(payload); + + gnosis.selectFork(); + + assertEq(IL2BridgeExecutor(Gnosis.AMB_EXECUTOR).getActionsSetCount(), 5); + + gnosis.relayFromHost(true); + skip(2 days); + + assertEq(IL2BridgeExecutor(Gnosis.AMB_EXECUTOR).getActionsSetCount(), 6); + + assertEq(createConfigurationSnapshot('', IPool(Gnosis.POOL)).length, 8); + + IL2BridgeExecutor(Gnosis.AMB_EXECUTOR).execute(5); + + assertEq(createConfigurationSnapshot('', IPool(Gnosis.POOL)).length, 9); + } + + function testMorphoSupplyCapUpdates() public { + MarketParams memory susde1 = MarketParams({ + loanToken: Ethereum.DAI, + collateralToken: Ethereum.SUSDE, + oracle: Ethereum.MORPHO_SUSDE_ORACLE, + irm: Ethereum.MORPHO_DEFAULT_IRM, + lltv: 0.86e18 + }); + MarketParams memory susde2 = MarketParams({ + loanToken: Ethereum.DAI, + collateralToken: Ethereum.SUSDE, + oracle: Ethereum.MORPHO_SUSDE_ORACLE, + irm: Ethereum.MORPHO_DEFAULT_IRM, + lltv: 0.915e18 + }); + + _assertMorphoCap(susde1, 400_000_000e18); + _assertMorphoCap(susde2, 100_000_000e18); + + executePayload(payload); + + _assertMorphoCap(susde1, 400_000_000e18, 500_000_000e18); + _assertMorphoCap(susde2, 100_000_000e18, 200_000_000e18); + + assertEq(IMetaMorpho(Ethereum.MORPHO_VAULT_DAI_1).timelock(), 1 days); + + skip(1 days); + + // These are permissionless (call coming from the test contract) + IMetaMorpho(Ethereum.MORPHO_VAULT_DAI_1).acceptCap(susde1); + IMetaMorpho(Ethereum.MORPHO_VAULT_DAI_1).acceptCap(susde2); + + _assertMorphoCap(susde1, 500_000_000e18); + _assertMorphoCap(susde2, 200_000_000e18); + } + +} diff --git a/src/proposals/20240627/SparkGnosis_20240627.sol b/src/proposals/20240627/SparkGnosis_20240627.sol new file mode 100644 index 00000000..a61f7055 --- /dev/null +++ b/src/proposals/20240627/SparkGnosis_20240627.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; + +import { IERC20 } from 'lib/erc20-helpers/src/interfaces/IERC20.sol'; + +import { SparkPayloadGnosis, Gnosis, IEngine, Rates, EngineFlags } from 'src/SparkPayloadGnosis.sol'; + +/** + * @title June 27, 2024 Spark Gnosis Proposal + * @notice Onboard USDC.e market and update USDC market parameters + * @author Phoenix Labs + * Forum: https://forum.makerdao.com/t/jun-12-2024-proposed-changes-to-sparklend-for-upcoming-spell/24489 + * Votes: https://vote.makerdao.com/polling/QmQv9zQR + * https://vote.makerdao.com/polling/QmU6KSGc + * https://vote.makerdao.com/polling/QmdQYTQe + */ +contract SparkGnosis_20240627 is SparkPayloadGnosis { + + address public constant USDCE = 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0; + address public constant USDCE_PRICE_FEED = 0x6FC2871B6d9A94866B7260896257Fd5b50c09900; + + function newListings() + public pure override returns (IEngine.Listing[] memory) + { + IEngine.Listing[] memory listings = new IEngine.Listing[](1); + + listings[0] = IEngine.Listing({ + asset: USDCE, + assetSymbol: 'USDC.e', + priceFeed: USDCE_PRICE_FEED, + rateStrategyParams: Rates.RateStrategyParams({ + optimalUsageRatio: _bpsToRay(95_00), + baseVariableBorrowRate: 0, + variableRateSlope1: _bpsToRay(9_00), + variableRateSlope2: _bpsToRay(15_00), + stableRateSlope1: 0, + stableRateSlope2: 0, + baseStableRateOffset: 0, + stableRateExcessOffset: 0, + optimalStableToTotalDebtRatio: 0 + }), + enabledToBorrow: EngineFlags.ENABLED, + stableRateModeEnabled: EngineFlags.DISABLED, + borrowableInIsolation: EngineFlags.ENABLED, + withSiloedBorrowing: EngineFlags.DISABLED, + flashloanable: EngineFlags.ENABLED, + ltv: 0, + liqThreshold: 0, + liqBonus: 0, + reserveFactor: 10_00, + supplyCap: 10_000_000, + borrowCap: 8_000_000, + debtCeiling: 0, + liqProtocolFee: 0, + eModeCategory: 0 + }); + + return listings; + } + + function rateStrategiesUpdates() + public view override returns (IEngine.RateStrategyUpdate[] memory) + { + IEngine.RateStrategyUpdate[] memory ratesUpdate = new IEngine.RateStrategyUpdate[](1); + + Rates.RateStrategyParams memory usdcParams = LISTING_ENGINE + .RATE_STRATEGIES_FACTORY() + .getStrategyDataOfAsset(Gnosis.USDC); + usdcParams.optimalUsageRatio = _bpsToRay(80_00); + usdcParams.variableRateSlope2 = _bpsToRay(50_00); + ratesUpdate[0] = IEngine.RateStrategyUpdate({ + asset: Gnosis.USDC, + params: usdcParams + }); + + return ratesUpdate; + } + + function capsUpdates() + public pure override returns (IEngine.CapsUpdate[] memory) + { + IEngine.CapsUpdate[] memory capsUpdate = new IEngine.CapsUpdate[](1); + + capsUpdate[0] = IEngine.CapsUpdate({ + asset: Gnosis.USDC, + supplyCap: EngineFlags.KEEP_CURRENT, + borrowCap: 1_000_000 + }); + + return capsUpdate; + } + + function _postExecute() + internal override + { + // Making an initial deposit right after the listing to prevent spToken value manipulation + IERC20(USDCE).approve(address(LISTING_ENGINE.POOL()), 1e6); + LISTING_ENGINE.POOL().deposit(USDCE, 1e6, address(this), 0); + } + +} diff --git a/src/proposals/20240627/SparkGnosis_20240627.t.sol b/src/proposals/20240627/SparkGnosis_20240627.t.sol new file mode 100644 index 00000000..7f620b7b --- /dev/null +++ b/src/proposals/20240627/SparkGnosis_20240627.t.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; + +import '../../SparkTestBase.sol'; + +contract SparkGnosis_20240627Test is SparkGnosisTestBase { + + address public constant USDCE = 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0; + address public constant USDCE_PRICE_FEED = 0x6FC2871B6d9A94866B7260896257Fd5b50c09900; + + constructor() { + id = '20240627'; + } + + function setUp() public { + vm.createSelectFork(getChain('gnosis_chain').rpcUrl, 34563897); // June 20, 2024 + payload = 0xd5A8d293Ce8B31123C285d55d0232b3C31c4D217; + + loadPoolContext(poolAddressesProviderRegistry.getAddressesProvidersList()[0]); + } + + function testNewListing() public { + ReserveConfig[] memory allConfigsBefore = createConfigurationSnapshot('', pool); + + assertEq(allConfigsBefore.length, 8); + + executePayload(payload); + + ReserveConfig[] memory allConfigsAfter = createConfigurationSnapshot('', pool); + + assertEq(allConfigsAfter.length, 9); + + ReserveConfig memory usdce = ReserveConfig({ + symbol: 'USDC.e', + underlying: USDCE, + aToken: address(0), // Mock, as they don't get validated, because of the "dynamic" deployment on proposal execution + variableDebtToken: address(0), // Mock, as they don't get validated, because of the "dynamic" deployment on proposal execution + stableDebtToken: address(0), // Mock, as they don't get validated, because of the "dynamic" deployment on proposal execution + decimals: 6, + ltv: 0, + liquidationThreshold: 0, + liquidationBonus: 0, + liquidationProtocolFee: 0, + reserveFactor: 10_00, + usageAsCollateralEnabled: false, + borrowingEnabled: true, + interestRateStrategy: _findReserveConfigBySymbol(allConfigsAfter, 'USDC.e').interestRateStrategy, + stableBorrowRateEnabled: false, + isPaused: false, + isActive: true, + isFrozen: false, + isSiloed: false, + isBorrowableInIsolation: true, + isFlashloanable: true, + supplyCap: 10_000_000, + borrowCap: 8_000_000, + debtCeiling: 0, + eModeCategory: 0 + }); + + _validateReserveConfig(usdce, allConfigsAfter); + + _validateInterestRateStrategy( + usdce.interestRateStrategy, + usdce.interestRateStrategy, + InterestStrategyValues({ + addressesProvider: address(poolAddressesProvider), + optimalUsageRatio: 0.95e27, + optimalStableToTotalDebtRatio: 0, + baseStableBorrowRate: 0.09e27, + stableRateSlope1: 0, + stableRateSlope2: 0, + baseVariableBorrowRate: 0, + variableRateSlope1: 0.09e27, + variableRateSlope2: 0.15e27 + }) + ); + + _validateAssetSourceOnOracle(poolAddressesProvider, USDCE, USDCE_PRICE_FEED); + } + + function testExistingMarketUpdates() public { + ReserveConfig[] memory allConfigsBefore = createConfigurationSnapshot('', pool); + + ReserveConfig memory usdcConfigBefore = _findReserveConfigBySymbol(allConfigsBefore, 'USDC'); + + assertEq(usdcConfigBefore.borrowCap, 8_000_000); + + IDefaultInterestRateStrategy oldInterestRateStrategy = IDefaultInterestRateStrategy( + usdcConfigBefore.interestRateStrategy + ); + + InterestStrategyValues memory oldInterestStrategyValues = InterestStrategyValues({ + addressesProvider: address(poolAddressesProvider), + optimalUsageRatio: 0.95e27, + optimalStableToTotalDebtRatio: oldInterestRateStrategy.OPTIMAL_STABLE_TO_TOTAL_DEBT_RATIO(), + baseStableBorrowRate: oldInterestRateStrategy.getBaseStableBorrowRate(), + stableRateSlope1: oldInterestRateStrategy.getStableRateSlope1(), + stableRateSlope2: oldInterestRateStrategy.getStableRateSlope2(), + baseVariableBorrowRate: oldInterestRateStrategy.getBaseVariableBorrowRate(), + variableRateSlope1: oldInterestRateStrategy.getVariableRateSlope1(), + variableRateSlope2: 0.15e27 + }); + _validateInterestRateStrategy( + address(oldInterestRateStrategy), + address(oldInterestRateStrategy), + oldInterestStrategyValues + ); + + executePayload(payload); + + ReserveConfig[] memory allConfigsAfter = createConfigurationSnapshot('', pool); + + ReserveConfig memory usdcConfigAfter = _findReserveConfigBySymbol(allConfigsAfter, 'USDC'); + + usdcConfigBefore.borrowCap = 1_000_000; + usdcConfigBefore.interestRateStrategy = usdcConfigAfter.interestRateStrategy; + + _validateReserveConfig(usdcConfigBefore, allConfigsAfter); + + IDefaultInterestRateStrategy newInterestRateStrategy = IDefaultInterestRateStrategy( + usdcConfigAfter.interestRateStrategy + ); + + InterestStrategyValues memory newInterestStrategyValues = InterestStrategyValues({ + addressesProvider: address(poolAddressesProvider), + optimalUsageRatio: 0.80e27, + optimalStableToTotalDebtRatio: oldInterestRateStrategy.OPTIMAL_STABLE_TO_TOTAL_DEBT_RATIO(), + baseStableBorrowRate: oldInterestRateStrategy.getBaseStableBorrowRate(), + stableRateSlope1: oldInterestRateStrategy.getStableRateSlope1(), + stableRateSlope2: oldInterestRateStrategy.getStableRateSlope2(), + baseVariableBorrowRate: oldInterestRateStrategy.getBaseVariableBorrowRate(), + variableRateSlope1: oldInterestRateStrategy.getVariableRateSlope1(), + variableRateSlope2: 0.50e27 + }); + _validateInterestRateStrategy( + address(newInterestRateStrategy), + address(newInterestRateStrategy), + newInterestStrategyValues + ); + } + +}