diff --git a/diffs/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221_before_AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221_after.md b/diffs/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221_before_AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221_after.md
new file mode 100644
index 000000000..ca012223a
--- /dev/null
+++ b/diffs/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221_before_AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221_after.md
@@ -0,0 +1,172 @@
+## Reserve changes
+
+### Reserves added
+
+#### ezETH ([0x2416092f143378750bb29b79eD961ab195CcEea5](https://arbiscan.io/address/0x2416092f143378750bb29b79eD961ab195CcEea5))
+
+| description | value |
+| --- | --- |
+| decimals | 18 |
+| isActive | true |
+| isFrozen | false |
+| supplyCap | 1,750 ezETH |
+| borrowCap | 1 ezETH |
+| debtCeiling | 0 $ [0] |
+| isSiloed | false |
+| isFlashloanable | true |
+| oracle | [0x8Ed37B72300683c0482A595bfa80fFb793874b15](https://arbiscan.io/address/0x8Ed37B72300683c0482A595bfa80fFb793874b15) |
+| oracleDecimals | 8 |
+| oracleDescription | Capped ezETH / ETH / USD |
+| oracleLatestAnswer | 3787.98660028 |
+| usageAsCollateralEnabled | true |
+| ltv | 0.05 % [5] |
+| liquidationThreshold | 0.1 % [10] |
+| liquidationBonus | 7.5 % |
+| liquidationProtocolFee | 10 % [1000] |
+| reserveFactor | 15 % [1500] |
+| aToken | [0xEA1132120ddcDDA2F119e99Fa7A27a0d036F7Ac9](https://arbiscan.io/address/0xEA1132120ddcDDA2F119e99Fa7A27a0d036F7Ac9) |
+| aTokenImpl | [0x1Be1798b70aEe431c2986f7ff48d9D1fa350786a](https://arbiscan.io/address/0x1Be1798b70aEe431c2986f7ff48d9D1fa350786a) |
+| variableDebtToken | [0x1fFD28689DA7d0148ff0fCB669e9f9f0Fc13a219](https://arbiscan.io/address/0x1fFD28689DA7d0148ff0fCB669e9f9f0Fc13a219) |
+| variableDebtTokenImpl | [0x5E76E98E0963EcDC6A065d1435F84065b7523f39](https://arbiscan.io/address/0x5E76E98E0963EcDC6A065d1435F84065b7523f39) |
+| borrowingEnabled | false |
+| isBorrowableInIsolation | false |
+| interestRateStrategy | [0x429F16dBA3B9e1900087Cbaa7b50D38Bc60fB73F](https://arbiscan.io/address/0x429F16dBA3B9e1900087Cbaa7b50D38Bc60fB73F) |
+| aTokenName | Aave Arbitrum ezETH |
+| aTokenSymbol | aArbezETH |
+| aTokenUnderlyingBalance | 0.03 ezETH [30000000000000000] |
+| id | 17 |
+| isPaused | false |
+| variableDebtTokenName | Aave Arbitrum Variable Debt ezETH |
+| variableDebtTokenSymbol | variableDebtArbezETH |
+| virtualAccountingActive | true |
+| virtualBalance | 0.03 ezETH [30000000000000000] |
+| optimalUsageRatio | 45 % |
+| maxVariableBorrowRate | 307 % |
+| baseVariableBorrowRate | 0 % |
+| variableRateSlope1 | 7 % |
+| variableRateSlope2 | 300 % |
+| interestRate |  |
+
+
+## Emodes changed
+
+### EMode: Stablecoins(id: 1)
+
+
+
+### EMode: ETH correlated(id: 2)
+
+
+
+### EMode: ezETH wstETH(id: 3)
+
+| description | value before | value after |
+| --- | --- | --- |
+| eMode.label | - | ezETH wstETH |
+| eMode.ltv | - | 93 % |
+| eMode.liquidationThreshold | - | 95 % |
+| eMode.liquidationBonus | - | 1 % |
+| eMode.borrowableBitmap | - | wstETH |
+| eMode.collateralBitmap | - | ezETH |
+
+
+### EMode: ezETH Stablecoins(id: 4)
+
+| description | value before | value after |
+| --- | --- | --- |
+| eMode.label | - | ezETH Stablecoins |
+| eMode.ltv | - | 72 % |
+| eMode.liquidationThreshold | - | 75 % |
+| eMode.liquidationBonus | - | 7.5 % |
+| eMode.borrowableBitmap | - | USDT, USDC |
+| eMode.collateralBitmap | - | ezETH |
+
+
+## Raw diff
+
+```json
+{
+ "eModes": {
+ "3": {
+ "from": null,
+ "to": {
+ "borrowableBitmap": "256",
+ "collateralBitmap": "131072",
+ "eModeCategory": 3,
+ "label": "ezETH wstETH",
+ "liquidationBonus": 10100,
+ "liquidationThreshold": 9500,
+ "ltv": 9300
+ }
+ },
+ "4": {
+ "from": null,
+ "to": {
+ "borrowableBitmap": "4128",
+ "collateralBitmap": "131072",
+ "eModeCategory": 4,
+ "label": "ezETH Stablecoins",
+ "liquidationBonus": 10750,
+ "liquidationThreshold": 7500,
+ "ltv": 7200
+ }
+ }
+ },
+ "reserves": {
+ "0x2416092f143378750bb29b79eD961ab195CcEea5": {
+ "from": null,
+ "to": {
+ "aToken": "0xEA1132120ddcDDA2F119e99Fa7A27a0d036F7Ac9",
+ "aTokenImpl": "0x1Be1798b70aEe431c2986f7ff48d9D1fa350786a",
+ "aTokenName": "Aave Arbitrum ezETH",
+ "aTokenSymbol": "aArbezETH",
+ "aTokenUnderlyingBalance": "30000000000000000",
+ "borrowCap": 1,
+ "borrowingEnabled": false,
+ "debtCeiling": 0,
+ "decimals": 18,
+ "id": 17,
+ "interestRateStrategy": "0x429F16dBA3B9e1900087Cbaa7b50D38Bc60fB73F",
+ "isActive": true,
+ "isBorrowableInIsolation": false,
+ "isFlashloanable": true,
+ "isFrozen": false,
+ "isPaused": false,
+ "isSiloed": false,
+ "liquidationBonus": 10750,
+ "liquidationProtocolFee": 1000,
+ "liquidationThreshold": 10,
+ "ltv": 5,
+ "oracle": "0x8Ed37B72300683c0482A595bfa80fFb793874b15",
+ "oracleDecimals": 8,
+ "oracleDescription": "Capped ezETH / ETH / USD",
+ "oracleLatestAnswer": "378798660028",
+ "reserveFactor": 1500,
+ "supplyCap": 1750,
+ "symbol": "ezETH",
+ "underlying": "0x2416092f143378750bb29b79eD961ab195CcEea5",
+ "usageAsCollateralEnabled": true,
+ "variableDebtToken": "0x1fFD28689DA7d0148ff0fCB669e9f9f0Fc13a219",
+ "variableDebtTokenImpl": "0x5E76E98E0963EcDC6A065d1435F84065b7523f39",
+ "variableDebtTokenName": "Aave Arbitrum Variable Debt ezETH",
+ "variableDebtTokenSymbol": "variableDebtArbezETH",
+ "virtualAccountingActive": true,
+ "virtualBalance": "30000000000000000"
+ }
+ }
+ },
+ "strategies": {
+ "0x2416092f143378750bb29b79eD961ab195CcEea5": {
+ "from": null,
+ "to": {
+ "address": "0x429F16dBA3B9e1900087Cbaa7b50D38Bc60fB73F",
+ "baseVariableBorrowRate": "0",
+ "maxVariableBorrowRate": "3070000000000000000000000000",
+ "optimalUsageRatio": "450000000000000000000000000",
+ "variableRateSlope1": "70000000000000000000000000",
+ "variableRateSlope2": "3000000000000000000000000000"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3Base_GHOBaseLaunch_20241223_before_AaveV3Base_GHOBaseLaunch_20241223_after.md b/diffs/AaveV3Base_GHOBaseLaunch_20241223_before_AaveV3Base_GHOBaseLaunch_20241223_after.md
new file mode 100644
index 000000000..c15d3e2bc
--- /dev/null
+++ b/diffs/AaveV3Base_GHOBaseLaunch_20241223_before_AaveV3Base_GHOBaseLaunch_20241223_after.md
@@ -0,0 +1,5 @@
+## Raw diff
+
+```json
+{}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3Base_GHOBaseListing_20241223_before_AaveV3Base_GHOBaseListing_20241223_after.md b/diffs/AaveV3Base_GHOBaseListing_20241223_before_AaveV3Base_GHOBaseListing_20241223_after.md
new file mode 100644
index 000000000..54dc91605
--- /dev/null
+++ b/diffs/AaveV3Base_GHOBaseListing_20241223_before_AaveV3Base_GHOBaseListing_20241223_after.md
@@ -0,0 +1,110 @@
+## Reserve changes
+
+### Reserves added
+
+#### GHO ([0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee](https://basescan.org/address/0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee))
+
+| description | value |
+| --- | --- |
+| decimals | 18 |
+| isActive | true |
+| isFrozen | false |
+| supplyCap | 2,500,000 GHO |
+| borrowCap | 2,250,000 GHO |
+| debtCeiling | 0 $ [0] |
+| isSiloed | false |
+| isFlashloanable | true |
+| oracle | [0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73](https://basescan.org/address/0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73) |
+| oracleDecimals | 8 |
+| oracleLatestAnswer | 1 |
+| usageAsCollateralEnabled | false |
+| ltv | 0 % [0] |
+| liquidationThreshold | 0 % [0] |
+| liquidationBonus | 0 % |
+| liquidationProtocolFee | 0 % [0] |
+| reserveFactor | 10 % [1000] |
+| aToken | [0xDD5745756C2de109183c6B5bB886F9207bEF114D](https://basescan.org/address/0xDD5745756C2de109183c6B5bB886F9207bEF114D) |
+| aTokenImpl | [0x98F409Fc4A42F34AE3c326c7f48ED01ae8cAeC69](https://basescan.org/address/0x98F409Fc4A42F34AE3c326c7f48ED01ae8cAeC69) |
+| variableDebtToken | [0xbc4f5631f2843488792e4F1660d0A51Ba489bdBd](https://basescan.org/address/0xbc4f5631f2843488792e4F1660d0A51Ba489bdBd) |
+| variableDebtTokenImpl | [0x2425A746911128c2eAA7bEBDc9Bc452eE52208a1](https://basescan.org/address/0x2425A746911128c2eAA7bEBDc9Bc452eE52208a1) |
+| borrowingEnabled | true |
+| isBorrowableInIsolation | false |
+| interestRateStrategy | [0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5](https://basescan.org/address/0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5) |
+| aTokenName | Aave Base GHO |
+| aTokenSymbol | aBasGHO |
+| aTokenUnderlyingBalance | 1 GHO [1000000000000000000] |
+| id | 7 |
+| isPaused | false |
+| variableDebtTokenName | Aave Base Variable Debt GHO |
+| variableDebtTokenSymbol | variableDebtBasGHO |
+| virtualAccountingActive | true |
+| virtualBalance | 1 GHO [1000000000000000000] |
+| optimalUsageRatio | 90 % |
+| maxVariableBorrowRate | 77 % |
+| baseVariableBorrowRate | 0 % |
+| variableRateSlope1 | 12 % |
+| variableRateSlope2 | 65 % |
+| interestRate |  |
+
+
+## Raw diff
+
+```json
+{
+ "reserves": {
+ "0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee": {
+ "from": null,
+ "to": {
+ "aToken": "0xDD5745756C2de109183c6B5bB886F9207bEF114D",
+ "aTokenImpl": "0x98F409Fc4A42F34AE3c326c7f48ED01ae8cAeC69",
+ "aTokenName": "Aave Base GHO",
+ "aTokenSymbol": "aBasGHO",
+ "aTokenUnderlyingBalance": "1000000000000000000",
+ "borrowCap": 2250000,
+ "borrowingEnabled": true,
+ "debtCeiling": 0,
+ "decimals": 18,
+ "id": 7,
+ "interestRateStrategy": "0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5",
+ "isActive": true,
+ "isBorrowableInIsolation": false,
+ "isFlashloanable": true,
+ "isFrozen": false,
+ "isPaused": false,
+ "isSiloed": false,
+ "liquidationBonus": 0,
+ "liquidationProtocolFee": 0,
+ "liquidationThreshold": 0,
+ "ltv": 0,
+ "oracle": "0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73",
+ "oracleDecimals": 8,
+ "oracleLatestAnswer": "100000000",
+ "reserveFactor": 1000,
+ "supplyCap": 2500000,
+ "symbol": "GHO",
+ "underlying": "0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee",
+ "usageAsCollateralEnabled": false,
+ "variableDebtToken": "0xbc4f5631f2843488792e4F1660d0A51Ba489bdBd",
+ "variableDebtTokenImpl": "0x2425A746911128c2eAA7bEBDc9Bc452eE52208a1",
+ "variableDebtTokenName": "Aave Base Variable Debt GHO",
+ "variableDebtTokenSymbol": "variableDebtBasGHO",
+ "virtualAccountingActive": true,
+ "virtualBalance": "1000000000000000000"
+ }
+ }
+ },
+ "strategies": {
+ "0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee": {
+ "from": null,
+ "to": {
+ "address": "0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5",
+ "baseVariableBorrowRate": "0",
+ "maxVariableBorrowRate": "770000000000000000000000000",
+ "optimalUsageRatio": "900000000000000000000000000",
+ "variableRateSlope1": "120000000000000000000000000",
+ "variableRateSlope2": "650000000000000000000000000"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221_before_AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221_after.md b/diffs/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221_before_AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221_after.md
new file mode 100644
index 000000000..6513062b4
--- /dev/null
+++ b/diffs/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221_before_AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221_after.md
@@ -0,0 +1,168 @@
+## Reserve changes
+
+### Reserves added
+
+#### ezETH ([0x2416092f143378750bb29b79eD961ab195CcEea5](https://basescan.org/address/0x2416092f143378750bb29b79eD961ab195CcEea5))
+
+| description | value |
+| --- | --- |
+| decimals | 18 |
+| isActive | true |
+| isFrozen | false |
+| supplyCap | 1,200 ezETH |
+| borrowCap | 1 ezETH |
+| debtCeiling | 0 $ [0] |
+| isSiloed | false |
+| isFlashloanable | true |
+| oracle | [0x438e24f5FCDC1A66ecb25D19B5543e0Cb91A44D4](https://basescan.org/address/0x438e24f5FCDC1A66ecb25D19B5543e0Cb91A44D4) |
+| oracleDecimals | 8 |
+| oracleDescription | Capped ezETH / ETH / USD |
+| oracleLatestAnswer | 3785.26748692 |
+| usageAsCollateralEnabled | true |
+| ltv | 0.05 % [5] |
+| liquidationThreshold | 0.1 % [10] |
+| liquidationBonus | 7.5 % |
+| liquidationProtocolFee | 10 % [1000] |
+| reserveFactor | 15 % [1500] |
+| aToken | [0xDD5745756C2de109183c6B5bB886F9207bEF114D](https://basescan.org/address/0xDD5745756C2de109183c6B5bB886F9207bEF114D) |
+| aTokenImpl | [0x98F409Fc4A42F34AE3c326c7f48ED01ae8cAeC69](https://basescan.org/address/0x98F409Fc4A42F34AE3c326c7f48ED01ae8cAeC69) |
+| variableDebtToken | [0xbc4f5631f2843488792e4F1660d0A51Ba489bdBd](https://basescan.org/address/0xbc4f5631f2843488792e4F1660d0A51Ba489bdBd) |
+| variableDebtTokenImpl | [0x2425A746911128c2eAA7bEBDc9Bc452eE52208a1](https://basescan.org/address/0x2425A746911128c2eAA7bEBDc9Bc452eE52208a1) |
+| borrowingEnabled | false |
+| isBorrowableInIsolation | false |
+| interestRateStrategy | [0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5](https://basescan.org/address/0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5) |
+| aTokenName | Aave Base ezETH |
+| aTokenSymbol | aBasezETH |
+| aTokenUnderlyingBalance | 0.03 ezETH [30000000000000000] |
+| id | 7 |
+| isPaused | false |
+| variableDebtTokenName | Aave Base Variable Debt ezETH |
+| variableDebtTokenSymbol | variableDebtBasezETH |
+| virtualAccountingActive | true |
+| virtualBalance | 0.03 ezETH [30000000000000000] |
+| optimalUsageRatio | 45 % |
+| maxVariableBorrowRate | 307 % |
+| baseVariableBorrowRate | 0 % |
+| variableRateSlope1 | 7 % |
+| variableRateSlope2 | 300 % |
+| interestRate |  |
+
+
+## Emodes changed
+
+### EMode: ETH correlated(id: 1)
+
+
+
+### EMode: ezETH wstETH(id: 2)
+
+| description | value before | value after |
+| --- | --- | --- |
+| eMode.label | - | ezETH wstETH |
+| eMode.ltv | - | 93 % |
+| eMode.liquidationThreshold | - | 95 % |
+| eMode.liquidationBonus | - | 1 % |
+| eMode.borrowableBitmap | - | wstETH |
+| eMode.collateralBitmap | - | ezETH |
+
+
+### EMode: ezETH Stablecoins(id: 3)
+
+| description | value before | value after |
+| --- | --- | --- |
+| eMode.label | - | ezETH Stablecoins |
+| eMode.ltv | - | 72 % |
+| eMode.liquidationThreshold | - | 75 % |
+| eMode.liquidationBonus | - | 7.5 % |
+| eMode.borrowableBitmap | - | USDC |
+| eMode.collateralBitmap | - | ezETH |
+
+
+## Raw diff
+
+```json
+{
+ "eModes": {
+ "2": {
+ "from": null,
+ "to": {
+ "borrowableBitmap": "8",
+ "collateralBitmap": "128",
+ "eModeCategory": 2,
+ "label": "ezETH wstETH",
+ "liquidationBonus": 10100,
+ "liquidationThreshold": 9500,
+ "ltv": 9300
+ }
+ },
+ "3": {
+ "from": null,
+ "to": {
+ "borrowableBitmap": "16",
+ "collateralBitmap": "128",
+ "eModeCategory": 3,
+ "label": "ezETH Stablecoins",
+ "liquidationBonus": 10750,
+ "liquidationThreshold": 7500,
+ "ltv": 7200
+ }
+ }
+ },
+ "reserves": {
+ "0x2416092f143378750bb29b79eD961ab195CcEea5": {
+ "from": null,
+ "to": {
+ "aToken": "0xDD5745756C2de109183c6B5bB886F9207bEF114D",
+ "aTokenImpl": "0x98F409Fc4A42F34AE3c326c7f48ED01ae8cAeC69",
+ "aTokenName": "Aave Base ezETH",
+ "aTokenSymbol": "aBasezETH",
+ "aTokenUnderlyingBalance": "30000000000000000",
+ "borrowCap": 1,
+ "borrowingEnabled": false,
+ "debtCeiling": 0,
+ "decimals": 18,
+ "id": 7,
+ "interestRateStrategy": "0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5",
+ "isActive": true,
+ "isBorrowableInIsolation": false,
+ "isFlashloanable": true,
+ "isFrozen": false,
+ "isPaused": false,
+ "isSiloed": false,
+ "liquidationBonus": 10750,
+ "liquidationProtocolFee": 1000,
+ "liquidationThreshold": 10,
+ "ltv": 5,
+ "oracle": "0x438e24f5FCDC1A66ecb25D19B5543e0Cb91A44D4",
+ "oracleDecimals": 8,
+ "oracleDescription": "Capped ezETH / ETH / USD",
+ "oracleLatestAnswer": "378526748692",
+ "reserveFactor": 1500,
+ "supplyCap": 1200,
+ "symbol": "ezETH",
+ "underlying": "0x2416092f143378750bb29b79eD961ab195CcEea5",
+ "usageAsCollateralEnabled": true,
+ "variableDebtToken": "0xbc4f5631f2843488792e4F1660d0A51Ba489bdBd",
+ "variableDebtTokenImpl": "0x2425A746911128c2eAA7bEBDc9Bc452eE52208a1",
+ "variableDebtTokenName": "Aave Base Variable Debt ezETH",
+ "variableDebtTokenSymbol": "variableDebtBasezETH",
+ "virtualAccountingActive": true,
+ "virtualBalance": "30000000000000000"
+ }
+ }
+ },
+ "strategies": {
+ "0x2416092f143378750bb29b79eD961ab195CcEea5": {
+ "from": null,
+ "to": {
+ "address": "0x86AB1C62A8bf868E1b3E1ab87d587Aba6fbCbDC5",
+ "baseVariableBorrowRate": "0",
+ "maxVariableBorrowRate": "3070000000000000000000000000",
+ "optimalUsageRatio": "450000000000000000000000000",
+ "variableRateSlope1": "70000000000000000000000000",
+ "variableRateSlope2": "3000000000000000000000000000"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121_before_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121_after.md b/diffs/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121_before_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121_after.md
new file mode 100644
index 000000000..425d5f242
--- /dev/null
+++ b/diffs/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121_before_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121_after.md
@@ -0,0 +1,30 @@
+## Reserve changes
+
+### Reserves altered
+
+#### GHO ([0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f](https://etherscan.io/address/0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f))
+
+| description | value before | value after |
+| --- | --- | --- |
+| maxVariableBorrowRate | 63 % | 61 % |
+| baseVariableBorrowRate | 10.5 % | 8.5 % |
+| interestRate |  |  |
+
+## Raw diff
+
+```json
+{
+ "strategies": {
+ "0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f": {
+ "baseVariableBorrowRate": {
+ "from": "105000000000000000000000000",
+ "to": "85000000000000000000000000"
+ },
+ "maxVariableBorrowRate": {
+ "from": "630000000000000000000000000",
+ "to": "610000000000000000000000000"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106_before_AaveV3Ethereum_AaveV33SherlockContestFunding_20250106_after.md b/diffs/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106_before_AaveV3Ethereum_AaveV33SherlockContestFunding_20250106_after.md
new file mode 100644
index 000000000..05db47580
--- /dev/null
+++ b/diffs/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106_before_AaveV3Ethereum_AaveV33SherlockContestFunding_20250106_after.md
@@ -0,0 +1,30 @@
+## Reserve changes
+
+### Reserves altered
+
+#### USDC ([0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48))
+
+| description | value before | value after |
+| --- | --- | --- |
+| aTokenUnderlyingBalance | 376,581,863.4842 USDC [376581863484281] | 376,351,863.4842 USDC [376351863484281] |
+| virtualBalance | 376,575,525.5590 USDC [376575525559007] | 376,345,525.5590 USDC [376345525559007] |
+
+
+## Raw diff
+
+```json
+{
+ "reserves": {
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": {
+ "aTokenUnderlyingBalance": {
+ "from": "376581863484281",
+ "to": "376351863484281"
+ },
+ "virtualBalance": {
+ "from": "376575525559007",
+ "to": "376345525559007"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223_before_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223_after.md b/diffs/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223_before_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223_after.md
new file mode 100644
index 000000000..9cc4cd6ef
--- /dev/null
+++ b/diffs/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223_before_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223_after.md
@@ -0,0 +1,152 @@
+## Reserve changes
+
+### Reserves added
+
+#### LBTC ([0x8236a87084f8B84306f72007F36F2618A5634494](https://etherscan.io/address/0x8236a87084f8B84306f72007F36F2618A5634494))
+
+| description | value |
+| --- | --- |
+| decimals | 8 |
+| isActive | true |
+| isFrozen | false |
+| supplyCap | 800 LBTC |
+| borrowCap | 80 LBTC |
+| debtCeiling | 0 $ [0] |
+| isSiloed | false |
+| isFlashloanable | true |
+| oracle | [0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c) |
+| oracleDecimals | 8 |
+| oracleDescription | BTC / USD |
+| oracleLatestAnswer | 102127.88158314 |
+| usageAsCollateralEnabled | true |
+| ltv | 70 % [7000] |
+| liquidationThreshold | 75 % [7500] |
+| liquidationBonus | 8.5 % |
+| liquidationProtocolFee | 10 % [1000] |
+| reserveFactor | 50 % [5000] |
+| aToken | [0x65906988ADEe75306021C417a1A3458040239602](https://etherscan.io/address/0x65906988ADEe75306021C417a1A3458040239602) |
+| aTokenImpl | [0x7EfFD7b47Bfd17e52fB7559d3f924201b9DbfF3d](https://etherscan.io/address/0x7EfFD7b47Bfd17e52fB7559d3f924201b9DbfF3d) |
+| variableDebtToken | [0x68aeB290C7727D899B47c56d1c96AEAC475cD0dD](https://etherscan.io/address/0x68aeB290C7727D899B47c56d1c96AEAC475cD0dD) |
+| variableDebtTokenImpl | [0xaC725CB59D16C81061BDeA61041a8A5e73DA9EC6](https://etherscan.io/address/0xaC725CB59D16C81061BDeA61041a8A5e73DA9EC6) |
+| borrowingEnabled | true |
+| isBorrowableInIsolation | false |
+| interestRateStrategy | [0x9ec6F08190DeA04A54f8Afc53Db96134e5E3FdFB](https://etherscan.io/address/0x9ec6F08190DeA04A54f8Afc53Db96134e5E3FdFB) |
+| aTokenName | Aave Ethereum LBTC |
+| aTokenSymbol | aEthLBTC |
+| aTokenUnderlyingBalance | 0.001 LBTC [100000] |
+| id | 37 |
+| isPaused | false |
+| variableDebtTokenName | Aave Ethereum Variable Debt LBTC |
+| variableDebtTokenSymbol | variableDebtEthLBTC |
+| virtualAccountingActive | true |
+| virtualBalance | 0.001 LBTC [100000] |
+| optimalUsageRatio | 45 % |
+| maxVariableBorrowRate | 304 % |
+| baseVariableBorrowRate | 0 % |
+| variableRateSlope1 | 4 % |
+| variableRateSlope2 | 300 % |
+| interestRate |  |
+
+
+## Emodes changed
+
+### EMode: ETH correlated(id: 1)
+
+
+
+### EMode: sUSDe Stablecoins(id: 2)
+
+
+
+### EMode: rsETH LST main(id: 3)
+
+
+
+### EMode: LBTC / WBTC(id: 4)
+
+| description | value before | value after |
+| --- | --- | --- |
+| eMode.label | - | LBTC / WBTC |
+| eMode.ltv | - | 84 % |
+| eMode.liquidationThreshold | - | 86 % |
+| eMode.liquidationBonus | - | 3 % |
+| eMode.borrowableBitmap | - | WBTC |
+| eMode.collateralBitmap | - | LBTC |
+
+
+## Raw diff
+
+```json
+{
+ "eModes": {
+ "4": {
+ "from": null,
+ "to": {
+ "borrowableBitmap": "4",
+ "collateralBitmap": "137438953472",
+ "eModeCategory": 4,
+ "label": "LBTC / WBTC",
+ "liquidationBonus": 10300,
+ "liquidationThreshold": 8600,
+ "ltv": 8400
+ }
+ }
+ },
+ "reserves": {
+ "0x8236a87084f8B84306f72007F36F2618A5634494": {
+ "from": null,
+ "to": {
+ "aToken": "0x65906988ADEe75306021C417a1A3458040239602",
+ "aTokenImpl": "0x7EfFD7b47Bfd17e52fB7559d3f924201b9DbfF3d",
+ "aTokenName": "Aave Ethereum LBTC",
+ "aTokenSymbol": "aEthLBTC",
+ "aTokenUnderlyingBalance": "100000",
+ "borrowCap": 80,
+ "borrowingEnabled": true,
+ "debtCeiling": 0,
+ "decimals": 8,
+ "id": 37,
+ "interestRateStrategy": "0x9ec6F08190DeA04A54f8Afc53Db96134e5E3FdFB",
+ "isActive": true,
+ "isBorrowableInIsolation": false,
+ "isFlashloanable": true,
+ "isFrozen": false,
+ "isPaused": false,
+ "isSiloed": false,
+ "liquidationBonus": 10850,
+ "liquidationProtocolFee": 1000,
+ "liquidationThreshold": 7500,
+ "ltv": 7000,
+ "oracle": "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c",
+ "oracleDecimals": 8,
+ "oracleDescription": "BTC / USD",
+ "oracleLatestAnswer": "10212788158314",
+ "reserveFactor": 5000,
+ "supplyCap": 800,
+ "symbol": "LBTC",
+ "underlying": "0x8236a87084f8B84306f72007F36F2618A5634494",
+ "usageAsCollateralEnabled": true,
+ "variableDebtToken": "0x68aeB290C7727D899B47c56d1c96AEAC475cD0dD",
+ "variableDebtTokenImpl": "0xaC725CB59D16C81061BDeA61041a8A5e73DA9EC6",
+ "variableDebtTokenName": "Aave Ethereum Variable Debt LBTC",
+ "variableDebtTokenSymbol": "variableDebtEthLBTC",
+ "virtualAccountingActive": true,
+ "virtualBalance": "100000"
+ }
+ }
+ },
+ "strategies": {
+ "0x8236a87084f8B84306f72007F36F2618A5634494": {
+ "from": null,
+ "to": {
+ "address": "0x9ec6F08190DeA04A54f8Afc53Db96134e5E3FdFB",
+ "baseVariableBorrowRate": "0",
+ "maxVariableBorrowRate": "3040000000000000000000000000",
+ "optimalUsageRatio": "450000000000000000000000000",
+ "variableRateSlope1": "40000000000000000000000000",
+ "variableRateSlope2": "3000000000000000000000000000"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117_before_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117_after.md b/diffs/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117_before_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117_after.md
new file mode 100644
index 000000000..c15d3e2bc
--- /dev/null
+++ b/diffs/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117_before_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117_after.md
@@ -0,0 +1,5 @@
+## Raw diff
+
+```json
+{}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3Ethereum_karpatkeyGhoGrowth_20241231_before_AaveV3Ethereum_karpatkeyGhoGrowth_20241231_after.md b/diffs/AaveV3Ethereum_karpatkeyGhoGrowth_20241231_before_AaveV3Ethereum_karpatkeyGhoGrowth_20241231_after.md
new file mode 100644
index 000000000..c15d3e2bc
--- /dev/null
+++ b/diffs/AaveV3Ethereum_karpatkeyGhoGrowth_20241231_before_AaveV3Ethereum_karpatkeyGhoGrowth_20241231_after.md
@@ -0,0 +1,5 @@
+## Raw diff
+
+```json
+{}
+```
\ No newline at end of file
diff --git a/diffs/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110_before_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110_after.md b/diffs/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110_before_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110_after.md
new file mode 100644
index 000000000..2cf287f03
--- /dev/null
+++ b/diffs/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110_before_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110_after.md
@@ -0,0 +1,242 @@
+## Reserve changes
+
+### Reserve added
+
+#### sUSDe ([0xAD17Da2f6Ac76746EF261E835C50b2651ce36DA8](https://era.zksync.network//address/0xAD17Da2f6Ac76746EF261E835C50b2651ce36DA8))
+
+| description | value |
+| --- | --- |
+| decimals | 18 |
+| isActive | true |
+| isFrozen | false |
+| supplyCap | 400,000 sUSDe |
+| borrowCap | 1 sUSDe |
+| debtCeiling | 400,000 $ [40000000] |
+| isSiloed | false |
+| isFlashloanable | true |
+| oracle | [0xDaec4cC3a41E423d678428A8Bb29fa1ADF26869a](https://era.zksync.network//address/0xDaec4cC3a41E423d678428A8Bb29fa1ADF26869a) |
+| oracleDecimals | 8 |
+| oracleDescription | Capped sUSDe / USDe / USD |
+| oracleLatestAnswer | 1.14468351 |
+| usageAsCollateralEnabled | true |
+| ltv | 65 % [6500] |
+| liquidationThreshold | 75 % [7500] |
+| liquidationBonus | 8.5 % |
+| liquidationProtocolFee | 10 % [1000] |
+| reserveFactor | 20 % [2000] |
+| aToken | [0xF3c9d58B76AC6Ee6811520021e9A9318c49E4CFa](https://era.zksync.network//address/0xF3c9d58B76AC6Ee6811520021e9A9318c49E4CFa) |
+| aTokenImpl | [0x34be365Fd01ac224F21490aaC6dFd65D25434bbB](https://era.zksync.network//address/0x34be365Fd01ac224F21490aaC6dFd65D25434bbB) |
+| variableDebtToken | [0xDeBb4ddaaaB1676775214552a7a05D6A13f905Da](https://era.zksync.network//address/0xDeBb4ddaaaB1676775214552a7a05D6A13f905Da) |
+| variableDebtTokenImpl | [0x52E97425D1Fa6885fAaC9260B711fA5047A88d06](https://era.zksync.network//address/0x52E97425D1Fa6885fAaC9260B711fA5047A88d06) |
+| borrowingEnabled | false |
+| isBorrowableInIsolation | false |
+| interestRateStrategy | [0x57815Ab06D846d7dECd326Ee541CD06144FED237](https://era.zksync.network//address/0x57815Ab06D846d7dECd326Ee541CD06144FED237) |
+| aTokenName | Aave ZkSync sUSDe |
+| aTokenSymbol | aZkssUSDe |
+| aTokenUnderlyingBalance | 100 sUSDe [100000000000000000000] |
+| id | 6 |
+| isPaused | false |
+| variableDebtTokenName | Aave ZkSync Variable Debt sUSDe |
+| variableDebtTokenSymbol | variableDebtZkssUSDe |
+| virtualAccountingActive | true |
+| virtualBalance | 100 sUSDe [100000000000000000000] |
+| optimalUsageRatio | 80 % |
+| maxVariableBorrowRate | 84 % |
+| baseVariableBorrowRate | 0 % |
+| variableRateSlope1 | 9 % |
+| variableRateSlope2 | 75 % |
+| interestRate |  |
+
+
+#### weETH ([0xc1Fa6E2E8667d9bE0Ca938a54c7E0285E9Df924a](https://era.zksync.network//address/0xc1Fa6E2E8667d9bE0Ca938a54c7E0285E9Df924a))
+
+| description | value |
+| --- | --- |
+| decimals | 18 |
+| isActive | true |
+| isFrozen | false |
+| supplyCap | 300 weETH |
+| borrowCap | 1 weETH |
+| debtCeiling | 0 $ [0] |
+| isSiloed | false |
+| isFlashloanable | true |
+| oracle | [0x32aF9A0a47B332761c8C90E9eC9f53e46e852b2B](https://era.zksync.network//address/0x32aF9A0a47B332761c8C90E9eC9f53e46e852b2B) |
+| oracleDecimals | 8 |
+| oracleDescription | Capped weETH / eETH(ETH) / USD |
+| oracleLatestAnswer | 3441.51796452 |
+| usageAsCollateralEnabled | true |
+| ltv | 72.5 % [7250] |
+| liquidationThreshold | 75 % [7500] |
+| liquidationBonus | 7.5 % |
+| liquidationProtocolFee | 10 % [1000] |
+| reserveFactor | 45 % [4500] |
+| aToken | [0xE818A67EE5c0531AFaa31Aa6e20bcAC36227A641](https://era.zksync.network//address/0xE818A67EE5c0531AFaa31Aa6e20bcAC36227A641) |
+| aTokenImpl | [0x34be365Fd01ac224F21490aaC6dFd65D25434bbB](https://era.zksync.network//address/0x34be365Fd01ac224F21490aaC6dFd65D25434bbB) |
+| variableDebtToken | [0xf31E1599b4480d07Fa96a7248c4f05cA84DA7fa8](https://era.zksync.network//address/0xf31E1599b4480d07Fa96a7248c4f05cA84DA7fa8) |
+| variableDebtTokenImpl | [0x52E97425D1Fa6885fAaC9260B711fA5047A88d06](https://era.zksync.network//address/0x52E97425D1Fa6885fAaC9260B711fA5047A88d06) |
+| borrowingEnabled | false |
+| isBorrowableInIsolation | false |
+| interestRateStrategy | [0x57815Ab06D846d7dECd326Ee541CD06144FED237](https://era.zksync.network//address/0x57815Ab06D846d7dECd326Ee541CD06144FED237) |
+| aTokenName | Aave ZkSync weETH |
+| aTokenSymbol | aZksweETH |
+| aTokenUnderlyingBalance | 0.025 weETH [25000000000000000] |
+| id | 5 |
+| isPaused | false |
+| variableDebtTokenName | Aave ZkSync Variable Debt weETH |
+| variableDebtTokenSymbol | variableDebtZksweETH |
+| virtualAccountingActive | true |
+| virtualBalance | 0.025 weETH [25000000000000000] |
+| optimalUsageRatio | 30 % |
+| maxVariableBorrowRate | 307 % |
+| baseVariableBorrowRate | 0 % |
+| variableRateSlope1 | 7 % |
+| variableRateSlope2 | 300 % |
+| interestRate |  |
+
+
+## Emodes changed
+
+### EMode: ETH correlated(id: 1)
+
+
+
+### EMode: weETH correlated(id: 2)
+
+| description | value before | value after |
+| --- | --- | --- |
+| eMode.label | - | weETH correlated |
+| eMode.ltv | - | 90 % |
+| eMode.liquidationThreshold | - | 93 % |
+| eMode.liquidationBonus | - | 1 % |
+| eMode.borrowableBitmap | - | WETH |
+| eMode.collateralBitmap | - | weETH |
+
+
+## Raw diff
+
+```json
+{
+ "eModes": {
+ "2": {
+ "from": null,
+ "to": {
+ "borrowableBitmap": "4",
+ "collateralBitmap": "32",
+ "eModeCategory": 2,
+ "label": "weETH correlated",
+ "liquidationBonus": 10100,
+ "liquidationThreshold": 9300,
+ "ltv": 9000
+ }
+ }
+ },
+ "reserves": {
+ "0xAD17Da2f6Ac76746EF261E835C50b2651ce36DA8": {
+ "from": null,
+ "to": {
+ "aToken": "0xF3c9d58B76AC6Ee6811520021e9A9318c49E4CFa",
+ "aTokenImpl": "0x34be365Fd01ac224F21490aaC6dFd65D25434bbB",
+ "aTokenName": "Aave ZkSync sUSDe",
+ "aTokenSymbol": "aZkssUSDe",
+ "aTokenUnderlyingBalance": "100000000000000000000",
+ "borrowCap": 1,
+ "borrowingEnabled": false,
+ "debtCeiling": 40000000,
+ "decimals": 18,
+ "id": 6,
+ "interestRateStrategy": "0x57815Ab06D846d7dECd326Ee541CD06144FED237",
+ "isActive": true,
+ "isBorrowableInIsolation": false,
+ "isFlashloanable": true,
+ "isFrozen": false,
+ "isPaused": false,
+ "isSiloed": false,
+ "liquidationBonus": 10850,
+ "liquidationProtocolFee": 1000,
+ "liquidationThreshold": 7500,
+ "ltv": 6500,
+ "oracle": "0xDaec4cC3a41E423d678428A8Bb29fa1ADF26869a",
+ "oracleDecimals": 8,
+ "oracleDescription": "Capped sUSDe / USDe / USD",
+ "oracleLatestAnswer": "114468351",
+ "reserveFactor": 2000,
+ "supplyCap": 400000,
+ "symbol": "sUSDe",
+ "underlying": "0xAD17Da2f6Ac76746EF261E835C50b2651ce36DA8",
+ "usageAsCollateralEnabled": true,
+ "variableDebtToken": "0xDeBb4ddaaaB1676775214552a7a05D6A13f905Da",
+ "variableDebtTokenImpl": "0x52E97425D1Fa6885fAaC9260B711fA5047A88d06",
+ "variableDebtTokenName": "Aave ZkSync Variable Debt sUSDe",
+ "variableDebtTokenSymbol": "variableDebtZkssUSDe",
+ "virtualAccountingActive": true,
+ "virtualBalance": "100000000000000000000"
+ }
+ },
+ "0xc1Fa6E2E8667d9bE0Ca938a54c7E0285E9Df924a": {
+ "from": null,
+ "to": {
+ "aToken": "0xE818A67EE5c0531AFaa31Aa6e20bcAC36227A641",
+ "aTokenImpl": "0x34be365Fd01ac224F21490aaC6dFd65D25434bbB",
+ "aTokenName": "Aave ZkSync weETH",
+ "aTokenSymbol": "aZksweETH",
+ "aTokenUnderlyingBalance": "25000000000000000",
+ "borrowCap": 1,
+ "borrowingEnabled": false,
+ "debtCeiling": 0,
+ "decimals": 18,
+ "id": 5,
+ "interestRateStrategy": "0x57815Ab06D846d7dECd326Ee541CD06144FED237",
+ "isActive": true,
+ "isBorrowableInIsolation": false,
+ "isFlashloanable": true,
+ "isFrozen": false,
+ "isPaused": false,
+ "isSiloed": false,
+ "liquidationBonus": 10750,
+ "liquidationProtocolFee": 1000,
+ "liquidationThreshold": 7500,
+ "ltv": 7250,
+ "oracle": "0x32aF9A0a47B332761c8C90E9eC9f53e46e852b2B",
+ "oracleDecimals": 8,
+ "oracleDescription": "Capped weETH / eETH(ETH) / USD",
+ "oracleLatestAnswer": "344151796452",
+ "reserveFactor": 4500,
+ "supplyCap": 300,
+ "symbol": "weETH",
+ "underlying": "0xc1Fa6E2E8667d9bE0Ca938a54c7E0285E9Df924a",
+ "usageAsCollateralEnabled": true,
+ "variableDebtToken": "0xf31E1599b4480d07Fa96a7248c4f05cA84DA7fa8",
+ "variableDebtTokenImpl": "0x52E97425D1Fa6885fAaC9260B711fA5047A88d06",
+ "variableDebtTokenName": "Aave ZkSync Variable Debt weETH",
+ "variableDebtTokenSymbol": "variableDebtZksweETH",
+ "virtualAccountingActive": true,
+ "virtualBalance": "25000000000000000"
+ }
+ }
+ },
+ "strategies": {
+ "0xAD17Da2f6Ac76746EF261E835C50b2651ce36DA8": {
+ "from": null,
+ "to": {
+ "address": "0x57815Ab06D846d7dECd326Ee541CD06144FED237",
+ "baseVariableBorrowRate": "0",
+ "maxVariableBorrowRate": "840000000000000000000000000",
+ "optimalUsageRatio": "800000000000000000000000000",
+ "variableRateSlope1": "90000000000000000000000000",
+ "variableRateSlope2": "750000000000000000000000000"
+ }
+ },
+ "0xc1Fa6E2E8667d9bE0Ca938a54c7E0285E9Df924a": {
+ "from": null,
+ "to": {
+ "address": "0x57815Ab06D846d7dECd326Ee541CD06144FED237",
+ "baseVariableBorrowRate": "0",
+ "maxVariableBorrowRate": "3070000000000000000000000000",
+ "optimalUsageRatio": "300000000000000000000000000",
+ "variableRateSlope1": "70000000000000000000000000",
+ "variableRateSlope2": "3000000000000000000000000000"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/foundry.toml b/foundry.toml
index 42e201d77..ec2034e8f 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -3,6 +3,8 @@ src = 'src'
test = 'tests'
script = 'scripts'
solc = '0.8.20'
+optimizer = true
+optimizer_runs = 200
out = 'out'
bytecode_hash = 'none'
libs = ['lib']
@@ -19,6 +21,8 @@ src = 'zksync'
test = 'zksync'
libs = ['lib']
solc = '0.8.20'
+optimizer = true
+optimizer_runs = 200
fs_permissions = [{ access = "write", path = "./reports" }]
ffi = true
evm_version = 'shanghai'
@@ -75,18 +79,18 @@ scroll = "${RPC_SCROLL}"
zksync = "${RPC_ZKSYNC}"
[etherscan]
-mainnet = { key="${ETHERSCAN_API_KEY_MAINNET}", chain=1 }
-optimism = { key="${ETHERSCAN_API_KEY_OPTIMISM}", chain=10 }
-avalanche = { key="${ETHERSCAN_API_KEY_AVALANCHE}", chain=43114 }
-polygon = { key="${ETHERSCAN_API_KEY_POLYGON}", chain=137 }
-arbitrum = { key="${ETHERSCAN_API_KEY_ARBITRUM}", chain=42161 }
-fantom = { key="${ETHERSCAN_API_KEY_FANTOM}", chain=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, url='https://api.bscscan.com/api'}
-scroll = { key="${ETHERSCAN_API_KEY_SCROLL}", chainId=534352 }
-zksync = { key="${ETHERSCAN_API_KEY_ZKSYNC}", chain = 324 }
+mainnet = { key = "${ETHERSCAN_API_KEY_MAINNET}", chain = 1 }
+optimism = { key = "${ETHERSCAN_API_KEY_OPTIMISM}", chain = 10 }
+avalanche = { key = "${ETHERSCAN_API_KEY_AVALANCHE}", chain = 43114 }
+polygon = { key = "${ETHERSCAN_API_KEY_POLYGON}", chain = 137 }
+arbitrum = { key = "${ETHERSCAN_API_KEY_ARBITRUM}", chain = 42161 }
+fantom = { key = "${ETHERSCAN_API_KEY_FANTOM}", chain = 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, url = 'https://api.bscscan.com/api' }
+scroll = { key = "${ETHERSCAN_API_KEY_SCROLL}", chainId = 534352 }
+zksync = { key = "${ETHERSCAN_API_KEY_ZKSYNC}", chain = 324 }
# See more config options https://github.com/gakonst/foundry/tree/master/config
diff --git a/generator/features/assetListing.ts b/generator/features/assetListing.ts
index 48dafe6ae..1c139f072 100644
--- a/generator/features/assetListing.ts
+++ b/generator/features/assetListing.ts
@@ -201,7 +201,7 @@ export const assetListing: FeatureModule
= {
listingTemplate += `| Borrowable in Isolation | ${cfg.borrowableInIsolation} |\n`;
listingTemplate += `| Oracle | ${cfg.priceFeed} |\n`;
if (isAddress(cfg.admin)) {
- listingTemplate += `\nAdditionaly [${cfg.admin}](${getExplorerLink(CHAIN_TO_CHAIN_ID[getPoolChain(pool)], cfg.admin)}) has been set as the emission admin for ${cfg.assetSymbol} and the corresponding aToken.\n`;
+ listingTemplate += `\nAdditionally [${cfg.admin}](${getExplorerLink(CHAIN_TO_CHAIN_ID[getPoolChain(pool)], cfg.admin)}) has been set as the emission admin for ${cfg.assetSymbol} and the corresponding aToken.\n`;
}
return listingTemplate;
}),
diff --git a/lib/aave-helpers b/lib/aave-helpers
index 7cdcda651..fe818aa58 160000
--- a/lib/aave-helpers
+++ b/lib/aave-helpers
@@ -1 +1 @@
-Subproject commit 7cdcda6516b6c08d3f3cee9e27b541c179e66fe3
+Subproject commit fe818aa58585d3525c827f04751b527310e2439a
diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol
index a1b86e1b9..455a652ee 100644
--- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol
+++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol
@@ -19,7 +19,7 @@ import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
/**
* @title GHO CCIP 1.5.1 Upgrade
* @author Aave Labs
- * - Discussion: TODO
+ * - Discussion: https://governance.aave.com/t/technical-maintenance-proposals/15274/59
*/
contract AaveV3Arbitrum_GHOCCIP151Upgrade_20241209 is IProposalGenericExecutor {
uint64 public constant ETH_CHAIN_SELECTOR = 5009297550715157269;
diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol
index 212ae0857..faca9a22f 100644
--- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol
+++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol
@@ -89,16 +89,8 @@ contract AaveV3Arbitrum_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase {
event CCIPSendRequested(IInternal.EVM2EVMMessage message);
function setUp() public virtual {
- vm.createSelectFork(vm.rpcUrl('arbitrum'), 293345614);
+ vm.createSelectFork(vm.rpcUrl('arbitrum'), 293994020);
proposal = new AaveV3Arbitrum_GHOCCIP151Upgrade_20241209();
-
- // pre-req - chainlink transfers gho token pool ownership on token admin registry
- address CLL_OWNER = TOKEN_ADMIN_REGISTRY.owner();
- vm.startPrank(CLL_OWNER);
- TOKEN_ADMIN_REGISTRY.transferAdminRole(address(GHO), GovernanceV3Arbitrum.EXECUTOR_LVL_1);
- EXISTING_PROXY_POOL.transferOwnership(GovernanceV3Arbitrum.EXECUTOR_LVL_1);
- vm.stopPrank();
-
_validateConstants();
}
diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol
index 533474dab..b0695928d 100644
--- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol
+++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol
@@ -88,8 +88,8 @@ contract AaveV3E2E_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase {
event Minted(address indexed sender, address indexed recipient, uint256 amount);
function setUp() public virtual {
- l1.c.forkId = vm.createFork(vm.rpcUrl('mainnet'), 21581477);
- l2.c.forkId = vm.createFork(vm.rpcUrl('arbitrum'), 293345614);
+ l1.c.forkId = vm.createFork(vm.rpcUrl('mainnet'), 21594804);
+ l2.c.forkId = vm.createFork(vm.rpcUrl('arbitrum'), 293994020);
vm.selectFork(l1.c.forkId);
l1.proposal = new AaveV3Ethereum_GHOCCIP151Upgrade_20241209();
@@ -126,8 +126,6 @@ contract AaveV3E2E_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase {
l2.c.proxyPool = l2.existingTokenPool.getProxyPool();
_validateConfig({upgraded: false});
-
- _performPoolTransferCLLPreReq(); // rm once CLL performs this action
}
function _getTokenMessage(
@@ -263,26 +261,6 @@ contract AaveV3E2E_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase {
executePayload(vm, address(l2.proposal));
}
- function _performPoolTransferCLLPreReq() private {
- vm.selectFork(l1.c.forkId);
- vm.startPrank(l1.c.tokenAdminRegistry.owner());
- l1.c.tokenAdminRegistry.transferAdminRole(
- AaveV3EthereumAssets.GHO_UNDERLYING,
- GovernanceV3Ethereum.EXECUTOR_LVL_1
- );
- IProxyPool(l1.c.proxyPool).transferOwnership(GovernanceV3Ethereum.EXECUTOR_LVL_1);
- vm.stopPrank();
-
- vm.selectFork(l2.c.forkId);
- vm.startPrank(l2.c.tokenAdminRegistry.owner());
- l2.c.tokenAdminRegistry.transferAdminRole(
- AaveV3ArbitrumAssets.GHO_UNDERLYING,
- GovernanceV3Arbitrum.EXECUTOR_LVL_1
- );
- IProxyPool(l2.c.proxyPool).transferOwnership(GovernanceV3Arbitrum.EXECUTOR_LVL_1);
- vm.stopPrank();
- }
-
function _getOutboundRefillTime(uint256 amount) internal pure returns (uint256) {
return (amount / CCIP_RATE_LIMIT_REFILL_RATE) + 1; // account for rounding
}
diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol
index 9c9dd50c7..c06228b86 100644
--- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol
+++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol
@@ -13,7 +13,7 @@ import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
/**
* @title GHO CCIP 1.5.1 Upgrade
* @author Aave Labs
- * - Discussion: TODO
+ * - Discussion: https://governance.aave.com/t/technical-maintenance-proposals/15274/59
*/
contract AaveV3Ethereum_GHOCCIP151Upgrade_20241209 is IProposalGenericExecutor {
uint64 public constant ARB_CHAIN_SELECTOR = 4949039107694359620;
diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol
index b535de501..3e6fde3f3 100644
--- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol
+++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol
@@ -89,16 +89,8 @@ contract AaveV3Ethereum_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase {
error BridgeLimitExceeded(uint256 limit);
function setUp() public virtual {
- vm.createSelectFork(vm.rpcUrl('mainnet'), 21581477);
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 21594804);
proposal = new AaveV3Ethereum_GHOCCIP151Upgrade_20241209();
-
- // pre-req - chainlink transfers gho token pool ownership on token admin registry
- address CLL_OWNER = TOKEN_ADMIN_REGISTRY.owner();
- vm.startPrank(CLL_OWNER);
- TOKEN_ADMIN_REGISTRY.transferAdminRole(address(GHO), GovernanceV3Ethereum.EXECUTOR_LVL_1);
- EXISTING_PROXY_POOL.transferOwnership(GovernanceV3Ethereum.EXECUTOR_LVL_1);
- vm.stopPrank();
-
_validateConstants();
}
diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/GHOCCIP151Upgrade.md b/src/20241209_Multi_GHOCCIP151Upgrade/GHOCCIP151Upgrade.md
index fb8e2c778..231fb9852 100644
--- a/src/20241209_Multi_GHOCCIP151Upgrade/GHOCCIP151Upgrade.md
+++ b/src/20241209_Multi_GHOCCIP151Upgrade/GHOCCIP151Upgrade.md
@@ -1,21 +1,60 @@
---
-title: "GHO CCIP 1.5.1 Upgrade"
+title: "GHO Risk Stewards Update and GHO CCIP Integration Upgrade"
author: "Aave Labs"
-discussions: ""
+discussions: "https://governance.aave.com/t/technical-maintenance-proposals/15274/59"
---
## Simple Summary
+This AIP proposes to:
+
+1. update the GHO Risk Steward contracts to enhance the Risk Council’s user experience and align the design of the Risk Stewards implementations throughout the Aave Protocol.
+2. update the GHO CCIP Token Pools on Arbitrum and Ethereum to integrate them with latest version of CCIP (1.5.1) to leverage the full functionality of CCIP and prepare for future expansions to other chains.
+
## Motivation
+This AIP seeks to enhance the Aave user experience and align the design of the Risk Stewards implementation across the Aave Protocol. Additionally, the CCIP was recently upgraded to version 1.5.1, introducing a number of enhancements for cross-chain pool management. Currently, GHO CCIP Token Pools are based on version 1.4, though still compatible with 1.5.1.
+
+Aave Labs will provide technical support to maintain the GHO CCIP Token Pools functional, secured, and aligned with the latest updates, enabling GHO expansion to other networks when needed.
+
## Specification
+The proposal includes the following actions:
+
+Risk Stewards update:
+
+1. GhoAaveSteward: Remove the max cap of 25% configured by `GHO_BORROW_RATE_MAX`. While this limitation was sensible when applied to the Ethereum reserve only, it is not necessary for different instances of GHO when implemented as a regular reserve. Additionally, the Risk Stewards already have limitations and sanity checks in place to restrict capabilities during rates update.
+2. GhoCcipSteward: Add a missing getter for the timelock state of the CCIP.
+3. GhoBucketSteward: No modification, configure new token pool and retire permissions for the existing token pool.
+
+GHO CCIP Token Pools upgrade:
+
+1. Ownership maintenance of contracts:
+ 1. Accept ownership of new token pool contracts for GHO on each network.
+ 2. Assume Admin role for the GHO token in the CCIP TokenAdminRegistry contract on each network.
+ 3. Take ownership of the existing proxy pools (even though they'll be deprecated).
+2. Migrate Liquidity Between Old and New Token Pools:
+ 1. On Ethereum: Transfer locked GHO liquidity from the old LockReleaseTokenPool contract to the new one, and properly initialize the new contract to reflect the correct amount of bridged liquidity.
+ 2. On Arbitrum: Mint tokens on the new BurnMintTokenPool contract and burn tokens from the old pool using the newly introduced `directMint` and `directBurn` methods. This is necessary to offboard the old pool as a facilitator and enable the new pool to handle bridge transactions.
+3. Setup a token rate limit of 300,000 GHO capacity and 60 GHO per second refill rate (216,000 GHO per hour), as recommended by the Risk Provider ChaosLabs in the previous maintenance upgrade to v1.5, see [here](https://governance.aave.com/t/technical-maintenance-proposals/15274/54).
+4. Keep GhoStewards functional by validating they can execute actions over the new CCIP lane and remain fully operational.
+
## References
-- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol), [AaveV3Arbitrum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol)
-- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol), [AaveV3Arbitrum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol)
-- [Snapshot](TODO)
-- [Discussion](TODO)
+- Implementation: [GhoAaveSteward](https://github.com/aave/gho-core/blob/cf6ee42adc8b2e9ac8ffd1d70bd5b52f06e536b6/src/contracts/misc/GhoAaveSteward.sol), [GhoCcipSteward](https://github.com/aave/gho-core/blob/cf6ee42adc8b2e9ac8ffd1d70bd5b52f06e536b6/src/contracts/misc/GhoCcipSteward.sol), [UpgradeableLockReleaseTokenPool](https://github.com/aave/ccip/blob/d5c6cedde6fbca9890a92a55f2db80e94793d0ec/contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol), [UpgradeableTokenPool](https://github.com/aave/ccip/blob/d5c6cedde6fbca9890a92a55f2db80e94793d0ec/contracts/src/v0.8/ccip/pools/GHO/UpgradeableTokenPool.sol)
+
+- Contracts:
+
+ - Ethereum
+ - [UpgradeableLockReleaseTokenPool](https://etherscan.io/address/0x06179f7C1be40863405f374E7f5F8806c728660A)
+ - [GhoAaveSteward](https://etherscan.io/address/0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34)
+ - [GhoCcipSteward](https://etherscan.io/address/0xC5BcC58BE6172769ca1a78B8A45752E3C5059c39)
+ - Arbitrum
+ - [UpgradeableBurnMintTokenPool](https://arbiscan.io/address/0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB)
+ - [GhoAaveSteward](https://arbiscan.io/address/0xd2D586f849620ef042FE3aF52eAa10e9b78bf7De)
+ - [GhoCcipSteward](https://arbiscan.io/address/0xCd5ab470AaC5c13e1063ee700503f3346b7C90Db)
+
+- Discussion: [GHO CCIP Integration Maintenance (CCIP v1.5.1 upgrade)](https://governance.aave.com/t/technical-maintenance-proposals/15274/59), [Update GHO Risk Stewards](https://governance.aave.com/t/technical-maintenance-proposals/15274/60)
## Copyright
diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/config.ts b/src/20241209_Multi_GHOCCIP151Upgrade/config.ts
index 84435de4a..c340e7540 100644
--- a/src/20241209_Multi_GHOCCIP151Upgrade/config.ts
+++ b/src/20241209_Multi_GHOCCIP151Upgrade/config.ts
@@ -6,12 +6,12 @@ export const config: ConfigFile = {
shortName: 'GHOCCIP151Upgrade',
date: '20241209',
author: 'Aave Labs',
- discussion: '',
+ discussion: 'https://governance.aave.com/t/technical-maintenance-proposals/15274/59',
snapshot: 'Direct-to-AIP',
votingNetwork: 'POLYGON',
},
poolOptions: {
- AaveV3Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 21581477}},
- AaveV3Arbitrum: {configs: {OTHERS: {}}, cache: {blockNumber: 293345614}},
+ AaveV3Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 21594804}},
+ AaveV3Arbitrum: {configs: {OTHERS: {}}, cache: {blockNumber: 293994020}},
},
};
diff --git a/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol
new file mode 100644
index 000000000..58feeff40
--- /dev/null
+++ b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3Arbitrum, AaveV3ArbitrumAssets, AaveV3ArbitrumEModes} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3PayloadArbitrum} from 'aave-helpers/src/v3-config-engine/AaveV3PayloadArbitrum.sol';
+import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol';
+import {IAaveV3ConfigEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+/**
+ * @title Onboard ezETH to Arbitrum and Base Instances
+ * @author Aave Chan Initiative
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0xf9fa710414237636cba11187111773fac04f74eb1a562d2b50fca86cb72a778e
+ * - Discussion: https://governance.aave.com/t/arfc-onboard-ezeth-to-arbitrum-and-base-instances/19622
+ */
+contract AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221 is AaveV3PayloadArbitrum {
+ using SafeERC20 for IERC20;
+
+ address public constant ezETH = 0x2416092f143378750bb29b79eD961ab195CcEea5;
+ uint256 public constant ezETH_SEED_AMOUNT = 3e16;
+ address public constant ezETH_LM_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd;
+
+ function _postExecute() internal override {
+ IERC20(ezETH).forceApprove(address(AaveV3Arbitrum.POOL), ezETH_SEED_AMOUNT);
+ AaveV3Arbitrum.POOL.supply(ezETH, ezETH_SEED_AMOUNT, address(AaveV3Arbitrum.COLLECTOR), 0);
+
+ (address aezETH, , ) = AaveV3Arbitrum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ ezETH
+ );
+ IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).setEmissionAdmin(ezETH, ezETH_LM_ADMIN);
+ IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).setEmissionAdmin(aezETH, ezETH_LM_ADMIN);
+ }
+
+ function eModeCategoriesUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.EModeCategoryUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.EModeCategoryUpdate[]
+ memory eModeUpdates = new IAaveV3ConfigEngine.EModeCategoryUpdate[](2);
+
+ eModeUpdates[0] = IAaveV3ConfigEngine.EModeCategoryUpdate({
+ eModeCategory: 3,
+ ltv: 93_00,
+ liqThreshold: 95_00,
+ liqBonus: 1_00,
+ label: 'ezETH wstETH'
+ });
+
+ eModeUpdates[1] = IAaveV3ConfigEngine.EModeCategoryUpdate({
+ eModeCategory: 4,
+ ltv: 72_00,
+ liqThreshold: 75_00,
+ liqBonus: 7_50,
+ label: 'ezETH Stablecoins'
+ });
+
+ return eModeUpdates;
+ }
+ function assetsEModeUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.AssetEModeUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.AssetEModeUpdate[]
+ memory assetEModeUpdates = new IAaveV3ConfigEngine.AssetEModeUpdate[](5);
+
+ assetEModeUpdates[0] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: ezETH,
+ eModeCategory: 3,
+ borrowable: EngineFlags.DISABLED,
+ collateral: EngineFlags.ENABLED
+ });
+ assetEModeUpdates[1] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: ezETH,
+ eModeCategory: 4,
+ borrowable: EngineFlags.DISABLED,
+ collateral: EngineFlags.ENABLED
+ });
+ assetEModeUpdates[2] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: AaveV3ArbitrumAssets.wstETH_UNDERLYING,
+ eModeCategory: 3,
+ borrowable: EngineFlags.ENABLED,
+ collateral: EngineFlags.DISABLED
+ });
+ assetEModeUpdates[3] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: AaveV3ArbitrumAssets.USDT_UNDERLYING,
+ eModeCategory: 4,
+ borrowable: EngineFlags.ENABLED,
+ collateral: EngineFlags.DISABLED
+ });
+ assetEModeUpdates[4] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: AaveV3ArbitrumAssets.USDCn_UNDERLYING,
+ eModeCategory: 4,
+ borrowable: EngineFlags.ENABLED,
+ collateral: EngineFlags.DISABLED
+ });
+
+ return assetEModeUpdates;
+ }
+ function newListings() public pure override returns (IAaveV3ConfigEngine.Listing[] memory) {
+ IAaveV3ConfigEngine.Listing[] memory listings = new IAaveV3ConfigEngine.Listing[](1);
+
+ listings[0] = IAaveV3ConfigEngine.Listing({
+ asset: ezETH,
+ assetSymbol: 'ezETH',
+ priceFeed: 0x8Ed37B72300683c0482A595bfa80fFb793874b15,
+ enabledToBorrow: EngineFlags.DISABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
+ ltv: 5,
+ liqThreshold: 10,
+ liqBonus: 7_50,
+ reserveFactor: 15_00,
+ supplyCap: 1_750,
+ borrowCap: 1,
+ debtCeiling: 0,
+ liqProtocolFee: 10_00,
+ rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({
+ optimalUsageRatio: 45_00,
+ baseVariableBorrowRate: 0,
+ variableRateSlope1: 7_00,
+ variableRateSlope2: 300_00
+ })
+ });
+
+ return listings;
+ }
+}
diff --git a/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol
new file mode 100644
index 000000000..366e59e33
--- /dev/null
+++ b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers} from 'aave-helpers/src/GovV3Helpers.sol';
+import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221} from './AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol';
+
+/**
+ * @dev Test for AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221
+ * command: FOUNDRY_PROFILE=arbitrum forge test --match-path=src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol -vv
+ */
+contract AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221_Test is ProtocolV3TestBase {
+ AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('arbitrum'), 292661143);
+ proposal = new AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221',
+ AaveV3Arbitrum.POOL,
+ address(proposal)
+ );
+ }
+
+ function test_collectorHasezETHFunds() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aTokenAddress, , ) = AaveV3Arbitrum
+ .AAVE_PROTOCOL_DATA_PROVIDER
+ .getReserveTokensAddresses(proposal.ezETH());
+ assertGe(IERC20(aTokenAddress).balanceOf(address(AaveV3Arbitrum.COLLECTOR)), 3 * 10 ** 16);
+ }
+
+ function test_ezETHAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aezETH, , ) = AaveV3Arbitrum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.ezETH()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).getEmissionAdmin(proposal.ezETH()),
+ proposal.ezETH_LM_ADMIN()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).getEmissionAdmin(aezETH),
+ proposal.ezETH_LM_ADMIN()
+ );
+ }
+}
diff --git a/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol
new file mode 100644
index 000000000..faf7314db
--- /dev/null
+++ b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3Base, AaveV3BaseAssets, AaveV3BaseEModes} from 'aave-address-book/AaveV3Base.sol';
+import {AaveV3PayloadBase} from 'aave-helpers/src/v3-config-engine/AaveV3PayloadBase.sol';
+import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol';
+import {IAaveV3ConfigEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+/**
+ * @title Onboard ezETH to Arbitrum and Base Instances
+ * @author Aave Chan Initiative
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0xf9fa710414237636cba11187111773fac04f74eb1a562d2b50fca86cb72a778e
+ * - Discussion: https://governance.aave.com/t/arfc-onboard-ezeth-to-arbitrum-and-base-instances/19622
+ */
+contract AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221 is AaveV3PayloadBase {
+ using SafeERC20 for IERC20;
+
+ address public constant ezETH = 0x2416092f143378750bb29b79eD961ab195CcEea5;
+ uint256 public constant ezETH_SEED_AMOUNT = 3e16;
+ address public constant ezETH_LM_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd;
+
+ function _postExecute() internal override {
+ IERC20(ezETH).forceApprove(address(AaveV3Base.POOL), ezETH_SEED_AMOUNT);
+ AaveV3Base.POOL.supply(ezETH, ezETH_SEED_AMOUNT, address(AaveV3Base.COLLECTOR), 0);
+
+ (address aezETH, , ) = AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(ezETH);
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).setEmissionAdmin(ezETH, ezETH_LM_ADMIN);
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).setEmissionAdmin(aezETH, ezETH_LM_ADMIN);
+ }
+
+ function eModeCategoriesUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.EModeCategoryUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.EModeCategoryUpdate[]
+ memory eModeUpdates = new IAaveV3ConfigEngine.EModeCategoryUpdate[](2);
+
+ eModeUpdates[0] = IAaveV3ConfigEngine.EModeCategoryUpdate({
+ eModeCategory: 2,
+ ltv: 93_00,
+ liqThreshold: 95_00,
+ liqBonus: 1_00,
+ label: 'ezETH wstETH'
+ });
+
+ eModeUpdates[1] = IAaveV3ConfigEngine.EModeCategoryUpdate({
+ eModeCategory: 3,
+ ltv: 72_00,
+ liqThreshold: 75_00,
+ liqBonus: 7_50,
+ label: 'ezETH Stablecoins'
+ });
+
+ return eModeUpdates;
+ }
+ function assetsEModeUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.AssetEModeUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.AssetEModeUpdate[]
+ memory assetEModeUpdates = new IAaveV3ConfigEngine.AssetEModeUpdate[](4);
+
+ assetEModeUpdates[0] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: AaveV3BaseAssets.wstETH_UNDERLYING,
+ eModeCategory: 2,
+ borrowable: EngineFlags.ENABLED,
+ collateral: EngineFlags.DISABLED
+ });
+ assetEModeUpdates[1] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: ezETH,
+ eModeCategory: 3,
+ borrowable: EngineFlags.DISABLED,
+ collateral: EngineFlags.ENABLED
+ });
+ assetEModeUpdates[2] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: ezETH,
+ eModeCategory: 2,
+ borrowable: EngineFlags.DISABLED,
+ collateral: EngineFlags.ENABLED
+ });
+ assetEModeUpdates[3] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: AaveV3BaseAssets.USDC_UNDERLYING,
+ eModeCategory: 3,
+ borrowable: EngineFlags.ENABLED,
+ collateral: EngineFlags.DISABLED
+ });
+
+ return assetEModeUpdates;
+ }
+ function newListings() public pure override returns (IAaveV3ConfigEngine.Listing[] memory) {
+ IAaveV3ConfigEngine.Listing[] memory listings = new IAaveV3ConfigEngine.Listing[](1);
+
+ listings[0] = IAaveV3ConfigEngine.Listing({
+ asset: ezETH,
+ assetSymbol: 'ezETH',
+ priceFeed: 0x438e24f5FCDC1A66ecb25D19B5543e0Cb91A44D4,
+ enabledToBorrow: EngineFlags.DISABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
+ ltv: 5,
+ liqThreshold: 10,
+ liqBonus: 7_50,
+ reserveFactor: 15_00,
+ supplyCap: 1_200,
+ borrowCap: 1,
+ debtCeiling: 0,
+ liqProtocolFee: 10_00,
+ rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({
+ optimalUsageRatio: 45_00,
+ baseVariableBorrowRate: 0,
+ variableRateSlope1: 7_00,
+ variableRateSlope2: 300_00
+ })
+ });
+
+ return listings;
+ }
+}
diff --git a/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol
new file mode 100644
index 000000000..3dd1ef5a8
--- /dev/null
+++ b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers} from 'aave-helpers/src/GovV3Helpers.sol';
+import {AaveV3Base} from 'aave-address-book/AaveV3Base.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221} from './AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol';
+
+/**
+ * @dev Test for AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221
+ * command: FOUNDRY_PROFILE=base forge test --match-path=src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol -vv
+ */
+contract AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221_Test is ProtocolV3TestBase {
+ AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('base'), 24698319);
+ proposal = new AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221',
+ AaveV3Base.POOL,
+ address(proposal)
+ );
+ }
+
+ function test_collectorHasezETHFunds() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aTokenAddress, , ) = AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.ezETH()
+ );
+ assertGe(IERC20(aTokenAddress).balanceOf(address(AaveV3Base.COLLECTOR)), 3 * 10 ** 16);
+ }
+
+ function test_ezETHAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aezETH, , ) = AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.ezETH()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).getEmissionAdmin(proposal.ezETH()),
+ proposal.ezETH_LM_ADMIN()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).getEmissionAdmin(aezETH),
+ proposal.ezETH_LM_ADMIN()
+ );
+ }
+}
diff --git a/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances.md b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances.md
new file mode 100644
index 000000000..78615ed4c
--- /dev/null
+++ b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances.md
@@ -0,0 +1,87 @@
+---
+title: "Onboard ezETH to Arbitrum and Base Instances"
+author: "Aave Chan Initiative"
+discussions: "https://governance.aave.com/t/arfc-onboard-ezeth-to-arbitrum-and-base-instances/19622"
+snapshot: "https://snapshot.box/#/s:aave.eth/proposal/0xf9fa710414237636cba11187111773fac04f74eb1a562d2b50fca86cb72a778e"
+---
+
+## Simple Summary
+
+This is an AIP for adding borrow/lend support for Renzo Protocol and its Liquid Restaking Token (LRT) ezETH on AAVE V3 Base and Arbitrum.
+
+## Motivation
+
+As productive assets, staking tokens are high quality collateral to borrow against. ezETH is one of the largest liquid restaking tokens. With strong demand for ezETH on mainnet this proposal seeks to provide the opportunity for Aave users to make use of ezETH on leading Layer 2 networks: Base and Arbitrum.
+
+The onboarding of ezETH on these L2s will create increased ezETH demand and increased revenues for both Aave and Renzo Protocol, whilst also bolstering the liquidity and peg stability of ezETH.
+
+## Specification
+
+The table below illustrates the configured risk parameters for **ezETH** on Arbitrum and Base instances
+
+| Parameter | Value |
+| --------------------------- | -----------------------------------------: |
+| Isolation Mode | false |
+| Borrowable | DISABLED |
+| Collateral Enabled | true |
+| Supply Cap (ezETH) Arbitrum | 1,750 |
+| Supply Cap (ezETH) Base | 1,200 |
+| Borrow Cap (ezETH) | 1 |
+| Debt Ceiling | USD 0 |
+| LTV | 0.05 % |
+| LT | 0.10 % |
+| Liquidation Bonus | 7.5 % |
+| Liquidation Protocol Fee | 10 % |
+| Reserve Factor | 15 % |
+| Base Variable Borrow Rate | 0 % |
+| Variable Slope 1 | 7 % |
+| Variable Slope 2 | 300 % |
+| Uoptimal | 45 % |
+| Flashloanable | ENABLED |
+| Siloed Borrowing | DISABLED |
+| Borrowable in Isolation | DISABLED |
+| Oracle (Arbitrum) | 0x8Ed37B72300683c0482A595bfa80fFb793874b15 |
+| Oracle (Base) | 0x438e24f5FCDC1A66ecb25D19B5543e0Cb91A44D4 |
+
+The following CAPO and E-Mode parameters are applied to both instances
+
+### CAPO
+
+| **maxYearlyRatioGrowthPercent** | **ratioReferenceTime** | **MINIMUM_SNAPSHOT_DELAY** |
+| ------------------------------- | ---------------------- | -------------------------- |
+| 10.89% | monthly | 14 days |
+
+### E-mode: ezETH / wstETH
+
+| Parameter | Value | Value |
+| --------------------- | ----- | ------ |
+| Asset | ezETH | wstETH |
+| Collateral | Yes | No |
+| Borrowable | No | Yes |
+| Max LTV | 93% | 93% |
+| Liquidation Threshold | 95% | 95% |
+| Liquidation Penalty | 1.00% | 1.00% |
+
+### E-mode: ezETH / stablecoin
+
+| Parameter | Value | Value | Value (only on Arbitrum) |
+| --------------------- | ----- | ----- | ------------------------ |
+| Asset | ezETH | USDC | USDT |
+| Collateral | Yes | No | No |
+| Borrowable | No | Yes | Yes |
+| Max LTV | 72% | 72% | 72% |
+| Liquidation Threshold | 75% | 75% | 75% |
+| Liquidation Penalty | 7.50% | 7.50% | 7.50% |
+
+Additionaly [0xac140648435d03f784879cd789130F22Ef588Fcd](https://debank.com/profile/0xac140648435d03f784879cd789130F22Ef588Fcd) has been set as the emission admin for ezETH and the corresponding aToken on both instances.
+
+## References
+
+- Implementation: [AaveV3Arbitrum](https://github.com/bgd-labs/aave-proposals-v3/blob/612e087e1b3fff317fd444905d17bbf0e0abcb37/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol), [AaveV3Base](https://github.com/bgd-labs/aave-proposals-v3/blob/612e087e1b3fff317fd444905d17bbf0e0abcb37/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol)
+- Tests: [AaveV3Arbitrum](https://github.com/bgd-labs/aave-proposals-v3/blob/612e087e1b3fff317fd444905d17bbf0e0abcb37/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol), [AaveV3Base](https://github.com/bgd-labs/aave-proposals-v3/blob/612e087e1b3fff317fd444905d17bbf0e0abcb37/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.t.sol)
+- [Snapshot](https://snapshot.box/#/s:aave.eth/proposal/0xf9fa710414237636cba11187111773fac04f74eb1a562d2b50fca86cb72a778e)
+- [Discussion](https://governance.aave.com/t/arfc-onboard-ezeth-to-arbitrum-and-base-instances/19622)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol
new file mode 100644
index 000000000..eca2931a7
--- /dev/null
+++ b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript, ArbitrumScript, BaseScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221} from './AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol';
+import {AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221} from './AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221.sol';
+
+/**
+ * @dev Deploy Arbitrum
+ * deploy-command: make deploy-ledger contract=src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol:DeployArbitrum chain=arbitrum
+ * verify-command: FOUNDRY_PROFILE=arbitrum npx catapulta-verify -b broadcast/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol/42161/run-latest.json
+ */
+contract DeployArbitrum is ArbitrumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Deploy Base
+ * deploy-command: make deploy-ledger contract=src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol:DeployBase chain=base
+ * verify-command: FOUNDRY_PROFILE=base npx catapulta-verify -b broadcast/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol/8453/run-latest.json
+ */
+contract DeployBase is BaseScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances_20241221.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](2);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsArbitrum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsArbitrum[0] = GovV3Helpers.buildAction(
+ type(AaveV3Arbitrum_OnboardEzETHToArbitrumAndBaseInstances_20241221).creationCode
+ );
+ payloads[0] = GovV3Helpers.buildArbitrumPayload(vm, actionsArbitrum);
+
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsBase = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsBase[0] = GovV3Helpers.buildAction(
+ type(AaveV3Base_OnboardEzETHToArbitrumAndBaseInstances_20241221).creationCode
+ );
+ payloads[1] = GovV3Helpers.buildBasePayload(vm, actionsBase);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/OnboardEzETHToArbitrumAndBaseInstances.md'
+ )
+ );
+ }
+}
diff --git a/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/config.ts b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/config.ts
new file mode 100644
index 000000000..854abd168
--- /dev/null
+++ b/src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/config.ts
@@ -0,0 +1,108 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ configFile: 'src/20241221_Multi_OnboardEzETHToArbitrumAndBaseInstances/config.ts',
+ author: 'Aave Chan Initiative',
+ pools: ['AaveV3Arbitrum', 'AaveV3Base'],
+ title: 'Onboard ezETH to Arbitrum and Base Instances',
+ shortName: 'OnboardEzETHToArbitrumAndBaseInstances',
+ date: '20241221',
+ discussion:
+ 'https://governance.aave.com/t/arfc-onboard-ezeth-to-arbitrum-and-base-instances/19622',
+ snapshot:
+ 'https://snapshot.box/#/s:aave.eth/proposal/0xf9fa710414237636cba11187111773fac04f74eb1a562d2b50fca86cb72a778e',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {
+ AaveV3Arbitrum: {
+ configs: {
+ EMODES_UPDATES: [
+ {eModeCategory: 3, ltv: '93', liqThreshold: '95', liqBonus: '1', label: 'ezETH / wstETH'},
+ ],
+ EMODES_ASSETS: [
+ {asset: 'ezETH', eModeCategory: '3', collateral: 'ENABLED', borrowable: 'DISABLED'},
+ {
+ asset: 'ezETH',
+ eModeCategory: 'AaveV3ArbitrumEModes.ETH_CORRELATED',
+ collateral: 'ENABLED',
+ borrowable: 'DISABLED',
+ },
+ {asset: 'wstETH', eModeCategory: '3', collateral: 'DISABLED', borrowable: 'ENABLED'},
+ ],
+ ASSET_LISTING: [
+ {
+ assetSymbol: 'ezETH',
+ decimals: 18,
+ priceFeed: '0x8Ed37B72300683c0482A595bfa80fFb793874b15',
+ ltv: '72',
+ liqThreshold: '75',
+ liqBonus: '7.5',
+ debtCeiling: '0',
+ liqProtocolFee: '10',
+ enabledToBorrow: 'ENABLED',
+ flashloanable: 'ENABLED',
+ borrowableInIsolation: 'DISABLED',
+ withSiloedBorrowing: 'DISABLED',
+ reserveFactor: '15',
+ supplyCap: '1750',
+ borrowCap: '175',
+ rateStrategyParams: {
+ optimalUtilizationRate: '45',
+ baseVariableBorrowRate: '0',
+ variableRateSlope1: '7',
+ variableRateSlope2: '300',
+ },
+ asset: '0x2416092f143378750bb29b79eD961ab195CcEea5',
+ admin: '0xac140648435d03f784879cd789130F22Ef588Fcd',
+ },
+ ],
+ },
+ cache: {blockNumber: 287111756},
+ },
+ AaveV3Base: {
+ configs: {
+ EMODES_UPDATES: [
+ {eModeCategory: 2, ltv: '93', liqThreshold: '95', liqBonus: '1', label: 'ezETH / wstETH'},
+ ],
+ EMODES_ASSETS: [
+ {asset: 'wstETH', eModeCategory: '2', collateral: 'DISABLED', borrowable: 'ENABLED'},
+ {
+ asset: 'ezETH',
+ eModeCategory: 'AaveV3BaseEModes.ETH_CORRELATED',
+ collateral: 'ENABLED',
+ borrowable: 'DISABLED',
+ },
+ {asset: 'ezETH', eModeCategory: '2', collateral: 'ENABLED', borrowable: 'DISABLED'},
+ ],
+ ASSET_LISTING: [
+ {
+ assetSymbol: 'ezETH',
+ decimals: 18,
+ priceFeed: '0x438e24f5FCDC1A66ecb25D19B5543e0Cb91A44D4',
+ ltv: '72',
+ liqThreshold: '75',
+ liqBonus: '7.5',
+ debtCeiling: '0',
+ liqProtocolFee: '10',
+ enabledToBorrow: 'ENABLED',
+ flashloanable: 'ENABLED',
+ borrowableInIsolation: 'DISABLED',
+ withSiloedBorrowing: 'DISABLED',
+ reserveFactor: '15',
+ supplyCap: '1200',
+ borrowCap: '120',
+ rateStrategyParams: {
+ optimalUtilizationRate: '45',
+ baseVariableBorrowRate: '0',
+ variableRateSlope1: '7',
+ variableRateSlope2: '300',
+ },
+ asset: '0x2416092f143378750bb29b79ed961ab195cceea5',
+ admin: '0xac140648435d03f784879cd789130F22Ef588Fcd',
+ },
+ ],
+ },
+ cache: {blockNumber: 24002429},
+ },
+ },
+};
diff --git a/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.sol b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.sol
new file mode 100644
index 000000000..4377f42b3
--- /dev/null
+++ b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.sol
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {AaveV3PayloadEthereum} from 'aave-helpers/src/v3-config-engine/AaveV3PayloadEthereum.sol';
+import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol';
+import {IAaveV3ConfigEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+/**
+ * @title Onboard LBTC & Enable LBTC/WBTC liquid E-Mode on Aave v3 Core Instance
+ * @author Aave Chan Initiative
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x61f027ea797763c9e01736693570141a27a0a5d4517a6b63d0fd84474e8be995
+ * - Discussion: https://governance.aave.com/t/arfc-enable-lbtc-wbtc-liquid-e-mode-on-aave-v3-core-instance/20142
+ */
+contract AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223 is
+ AaveV3PayloadEthereum
+{
+ using SafeERC20 for IERC20;
+
+ address public constant LBTC = 0x8236a87084f8B84306f72007F36F2618A5634494;
+ uint256 public constant LBTC_SEED_AMOUNT = 1e5;
+ address public constant LBTC_LM_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd;
+
+ function _postExecute() internal override {
+ IERC20(LBTC).forceApprove(address(AaveV3Ethereum.POOL), LBTC_SEED_AMOUNT);
+ AaveV3Ethereum.POOL.supply(LBTC, LBTC_SEED_AMOUNT, address(AaveV3Ethereum.COLLECTOR), 0);
+
+ (address aLBTC, , ) = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ LBTC
+ );
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin(LBTC, LBTC_LM_ADMIN);
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin(aLBTC, LBTC_LM_ADMIN);
+ }
+
+ function eModeCategoriesUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.EModeCategoryUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.EModeCategoryUpdate[]
+ memory eModeUpdates = new IAaveV3ConfigEngine.EModeCategoryUpdate[](1);
+
+ eModeUpdates[0] = IAaveV3ConfigEngine.EModeCategoryUpdate({
+ eModeCategory: 4,
+ ltv: 84_00,
+ liqThreshold: 86_00,
+ liqBonus: 3_00,
+ label: 'LBTC / WBTC'
+ });
+
+ return eModeUpdates;
+ }
+ function assetsEModeUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.AssetEModeUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.AssetEModeUpdate[]
+ memory assetEModeUpdates = new IAaveV3ConfigEngine.AssetEModeUpdate[](2);
+
+ assetEModeUpdates[0] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: LBTC,
+ eModeCategory: 4,
+ borrowable: EngineFlags.DISABLED,
+ collateral: EngineFlags.ENABLED
+ });
+ assetEModeUpdates[1] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: AaveV3EthereumAssets.WBTC_UNDERLYING,
+ eModeCategory: 4,
+ borrowable: EngineFlags.ENABLED,
+ collateral: EngineFlags.DISABLED
+ });
+
+ return assetEModeUpdates;
+ }
+ function newListings() public pure override returns (IAaveV3ConfigEngine.Listing[] memory) {
+ IAaveV3ConfigEngine.Listing[] memory listings = new IAaveV3ConfigEngine.Listing[](1);
+
+ listings[0] = IAaveV3ConfigEngine.Listing({
+ asset: LBTC,
+ assetSymbol: 'LBTC',
+ priceFeed: 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c,
+ enabledToBorrow: EngineFlags.ENABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
+ ltv: 70_00,
+ liqThreshold: 75_00,
+ liqBonus: 8_50,
+ reserveFactor: 50_00,
+ supplyCap: 800,
+ borrowCap: 80,
+ debtCeiling: 0,
+ liqProtocolFee: 10_00,
+ rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({
+ optimalUsageRatio: 45_00,
+ baseVariableBorrowRate: 0,
+ variableRateSlope1: 4_00,
+ variableRateSlope2: 300_00
+ })
+ });
+
+ return listings;
+ }
+}
diff --git a/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.t.sol b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.t.sol
new file mode 100644
index 000000000..ccff96bf7
--- /dev/null
+++ b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.t.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers} from 'aave-helpers/src/GovV3Helpers.sol';
+import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223} from './AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.sol';
+
+/**
+ * @dev Test for AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223
+ * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.t.sol -vv
+ */
+contract AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223_Test is
+ ProtocolV3TestBase
+{
+ AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 21567986);
+ proposal = new AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223',
+ AaveV3Ethereum.POOL,
+ address(proposal)
+ );
+ }
+
+ function test_collectorHasLBTCFunds() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aTokenAddress, , ) = AaveV3Ethereum
+ .AAVE_PROTOCOL_DATA_PROVIDER
+ .getReserveTokensAddresses(proposal.LBTC());
+ assertGe(IERC20(aTokenAddress).balanceOf(address(AaveV3Ethereum.COLLECTOR)), 10 ** 5);
+ }
+
+ function test_LBTCAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aLBTC, , ) = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.LBTC()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).getEmissionAdmin(proposal.LBTC()),
+ proposal.LBTC_LM_ADMIN()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).getEmissionAdmin(aLBTC),
+ proposal.LBTC_LM_ADMIN()
+ );
+ }
+}
diff --git a/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance.md b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance.md
new file mode 100644
index 000000000..1cb904b63
--- /dev/null
+++ b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance.md
@@ -0,0 +1,74 @@
+---
+title: "Onboard LBTC & Enable LBTC/WBTC liquid E-Mode on Aave v3 Core Instance"
+author: "Aave Chan Initiative"
+discussions: "https://governance.aave.com/t/arfc-enable-lbtc-wbtc-liquid-e-mode-on-aave-v3-core-instance/20142"
+snapshot: "https://snapshot.box/#/s:aave.eth/proposal/0x61f027ea797763c9e01736693570141a27a0a5d4517a6b63d0fd84474e8be995"
+---
+
+## Simple Summary
+
+This proposal aims to onboard LBTC and enable LBTC/WBTC liquid E-Mode for the Main Instance. By implementing this change, we seek to enhance capital efficiency for borrowers using LBTC/WBTC as collateral.
+
+[TEMP CHECK](https://governance.aave.com/t/temp-check-onboard-enable-lbtc-wbtc-liquid-e-mode-on-aave-v3-core-instance/19968/7) and [TEMP CHECK Snapshot](https://snapshot.box/#/s:aave.eth/proposal/0x8fdee3ec7a301f9fba2e4c048227257070645d636b09a7afb369ee9c002ad764) have passed recently.
+
+## Motivation
+
+The motivation behind this proposal stems from several key factors:
+
+- High Utilization: LBTC/WBTC has demonstrated significant usage as collateral for borrowing stablecoins on other platforms.
+- Capital Efficiency: Enabling liquid E-Mode for LBTC/WBTC will allow borrowers to substantially improve their capital efficiency when using this asset as collateral.
+- Controlled Growth: Liquid E-Mode provides a mechanism for more precise control over the growth and borrow demand in relation to the overall stablecoin liquidity within Aave v3 on Core Instance.
+
+By implementing this proposal, we aim to optimize the use of LBTC/WBTC within the Aave ecosystem, attracting more liquidity for the protocol and increasing revenues.
+
+## Specification
+
+The table below illustrates the configured risk parameters for **LBTC**
+
+| Parameter | Value |
+| ------------------------- | -----------------------------------------: |
+| Isolation Mode | false |
+| Borrowable | ENABLED |
+| Collateral Enabled | true |
+| Supply Cap (LBTC) | 800 |
+| Borrow Cap (LBTC) | 80 |
+| Debt Ceiling | USD 0 |
+| LTV | 70 % |
+| LT | 75 % |
+| Liquidation Bonus | 8.5 % |
+| Liquidation Protocol Fee | 10 % |
+| Reserve Factor | 50 % |
+| Base Variable Borrow Rate | 0 % |
+| Variable Slope 1 | 4 % |
+| Variable Slope 2 | 300 % |
+| Uoptimal | 45 % |
+| Flashloanable | ENABLED |
+| Siloed Borrowing | DISABLED |
+| Borrowable in Isolation | DISABLED |
+| Oracle | 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c |
+
+Additionally [0xac140648435d03f784879cd789130F22Ef588Fcd](https://etherscan.io/address/0xac140648435d03f784879cd789130F22Ef588Fcd) has been set as the emission admin for LBTC and the corresponding aToken.
+
+**E-mode**
+
+the following E-mode will be created
+
+| Parameter | Value | Value |
+| --------------------- | ----- | ----- |
+| Asset | LBTC | WBTC |
+| Collateral | Yes | No |
+| Borrowable | No | Yes |
+| Max LTV | 84% | - |
+| Liquidation Threshold | 86% | - |
+| Liquidation Bonus | 3.00% | - |
+
+## References
+
+- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/97f99f8f3e9a0f6b53881ab3dfd8026d55d4b120/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.sol)
+- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/97f99f8f3e9a0f6b53881ab3dfd8026d55d4b120/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.t.sol)
+- [Snapshot](https://snapshot.box/#/s:aave.eth/proposal/0x61f027ea797763c9e01736693570141a27a0a5d4517a6b63d0fd84474e8be995)
+- [Discussion](https://governance.aave.com/t/arfc-enable-lbtc-wbtc-liquid-e-mode-on-aave-v3-core-instance/20142)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.s.sol b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.s.sol
new file mode 100644
index 000000000..82a2eeadc
--- /dev/null
+++ b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.s.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223} from './AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.sol';
+
+/**
+ * @dev Deploy Ethereum
+ * deploy-command: make deploy-ledger contract=src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.s.sol:DeployEthereum chain=mainnet
+ * verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.s.sol/1/run-latest.json
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(
+ type(AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance_20241223).creationCode
+ );
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance.md'
+ )
+ );
+ }
+}
diff --git a/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/config.ts b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/config.ts
new file mode 100644
index 000000000..1a00500fa
--- /dev/null
+++ b/src/20241223_AaveV3Ethereum_EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance/config.ts
@@ -0,0 +1,57 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ configFile: 'tmp.ts',
+ author: 'Aave Chan Initiative',
+ pools: ['AaveV3Ethereum'],
+ title: 'Onboard LBTC & enable LBTC/WBTC liquid E-Mode on Aave v3 Core Instance',
+ shortName: 'EnableLBTCWBTCLiquidEModeOnAavev3CoreInstance',
+ date: '20241223',
+ discussion:
+ 'https://governance.aave.com/t/arfc-enable-lbtc-wbtc-liquid-e-mode-on-aave-v3-core-instance/20142',
+ snapshot:
+ 'https://snapshot.box/#/s:aave.eth/proposal/0x61f027ea797763c9e01736693570141a27a0a5d4517a6b63d0fd84474e8be995',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {
+ AaveV3Ethereum: {
+ configs: {
+ EMODES_UPDATES: [
+ {eModeCategory: 4, ltv: '84', liqThreshold: '86', liqBonus: '3', label: 'LBTC / WBTC'},
+ ],
+ EMODES_ASSETS: [
+ {asset: 'LBTC', eModeCategory: '4', collateral: 'ENABLED', borrowable: 'DISABLED'},
+ {asset: 'WBTC', eModeCategory: '4', collateral: 'DISABLED', borrowable: 'ENABLED'},
+ ],
+ ASSET_LISTING: [
+ {
+ assetSymbol: 'LBTC',
+ decimals: 8,
+ priceFeed: '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c',
+ ltv: '70',
+ liqThreshold: '75',
+ liqBonus: '8.5',
+ debtCeiling: '0',
+ liqProtocolFee: '10',
+ enabledToBorrow: 'ENABLED',
+ flashloanable: 'ENABLED',
+ borrowableInIsolation: 'DISABLED',
+ withSiloedBorrowing: 'DISABLED',
+ reserveFactor: '50',
+ supplyCap: '800',
+ borrowCap: '80',
+ rateStrategyParams: {
+ optimalUtilizationRate: '45',
+ baseVariableBorrowRate: '0',
+ variableRateSlope1: '4',
+ variableRateSlope2: '300',
+ },
+ asset: '0x8236a87084f8B84306f72007F36F2618A5634494',
+ admin: '0xac140648435d03f784879cd789130F22Ef588Fcd',
+ },
+ ],
+ },
+ cache: {blockNumber: 21466630},
+ },
+ },
+};
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.sol
new file mode 100644
index 000000000..ce0dcaf5b
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.sol
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol';
+import {IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+import {ILegacyProxyAdmin, ITransparentUpgradeableProxy} from 'src/interfaces/ILegacyProxyAdmin.sol';
+
+import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol';
+
+/**
+ * @title GHO Base Launch
+ * @author Aave Labs
+ * - Discussion: https://governance.aave.com/t/arfc-launch-gho-on-base-set-aci-as-emissions-manager-for-rewards/19338
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x03dc21e0423c60082dc23317af6ebaf997610cbc2cbb0f5a385653bd99a524fe
+ */
+contract AaveV3Arbitrum_GHOBaseLaunch_20241223 is IProposalGenericExecutor {
+ uint64 public constant BASE_CHAIN_SELECTOR = 15971525489660198786;
+
+ // https://arbiscan.io/address/0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB
+ IUpgradeableBurnMintTokenPool_1_5_1 public constant TOKEN_POOL =
+ IUpgradeableBurnMintTokenPool_1_5_1(0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB);
+
+ // https://arbiscan.io/address/0x9f0e4F4c5664888442E459f40f635765BB6265Ec
+ address public constant NEW_GHO_TOKEN_PROXY_ADMIN = 0x9f0e4F4c5664888442E459f40f635765BB6265Ec;
+
+ // https://basescan.org/address/0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34
+ address public constant REMOTE_TOKEN_POOL_BASE = 0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34;
+ // https://basescan.org/address/0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee
+ address public constant REMOTE_GHO_TOKEN_BASE = 0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee;
+
+ // Token Rate Limit Capacity: 300_000 GHO
+ uint128 public constant CCIP_RATE_LIMIT_CAPACITY = 300_000e18;
+ // Token Rate Limit Refill Rate: 60 GHO per second (=> 216_000 GHO per hour)
+ uint128 public constant CCIP_RATE_LIMIT_REFILL_RATE = 60e18;
+
+ function execute() external {
+ // Update ProxyAdmin on GHO
+ ILegacyProxyAdmin(MiscArbitrum.PROXY_ADMIN).changeProxyAdmin(
+ ITransparentUpgradeableProxy(AaveV3ArbitrumAssets.GHO_UNDERLYING),
+ NEW_GHO_TOKEN_PROXY_ADMIN
+ );
+
+ IRateLimiter.Config memory rateLimiterConfig = IRateLimiter.Config({
+ isEnabled: true,
+ capacity: CCIP_RATE_LIMIT_CAPACITY,
+ rate: CCIP_RATE_LIMIT_REFILL_RATE
+ });
+
+ IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate[]
+ memory chainsToAdd = new IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate[](1);
+
+ bytes[] memory remotePoolAddresses = new bytes[](1);
+ remotePoolAddresses[0] = abi.encode(REMOTE_TOKEN_POOL_BASE);
+
+ chainsToAdd[0] = IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate({
+ remoteChainSelector: BASE_CHAIN_SELECTOR,
+ remotePoolAddresses: remotePoolAddresses,
+ remoteTokenAddress: abi.encode(REMOTE_GHO_TOKEN_BASE),
+ outboundRateLimiterConfig: rateLimiterConfig,
+ inboundRateLimiterConfig: rateLimiterConfig
+ });
+
+ TOKEN_POOL.applyChainUpdates({
+ remoteChainSelectorsToRemove: new uint64[](0),
+ chainsToAdd: chainsToAdd
+ });
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.t.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.t.sol
new file mode 100644
index 000000000..3c1cc9999
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.t.sol
@@ -0,0 +1,504 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import 'forge-std/Test.sol';
+
+import {IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol';
+import {IPool as IPool_CCIP} from 'src/interfaces/ccip/tokenPool/IPool.sol';
+import {IClient} from 'src/interfaces/ccip/IClient.sol';
+import {IInternal} from 'src/interfaces/ccip/IInternal.sol';
+import {IRouter} from 'src/interfaces/ccip/IRouter.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol';
+import {IEVM2EVMOffRamp_1_5} from 'src/interfaces/ccip/IEVM2EVMOffRamp.sol';
+import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol';
+import {IGhoToken} from 'src/interfaces/IGhoToken.sol';
+import {IGhoCcipSteward} from 'src/interfaces/IGhoCcipSteward.sol';
+
+import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {GovernanceV3Arbitrum} from 'aave-address-book/GovernanceV3Arbitrum.sol';
+import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol';
+
+import {ProxyAdmin, ITransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/ProxyAdmin.sol';
+
+import {CCIPUtils} from './utils/CCIPUtils.sol';
+
+import {AaveV3Arbitrum_GHOCCIP151Upgrade_20241209} from '../20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol';
+import {AaveV3Arbitrum_GHOBaseLaunch_20241223} from './AaveV3Arbitrum_GHOBaseLaunch_20241223.sol';
+
+/**
+ * @dev Test for AaveV3Arbitrum_GHOBaseLaunch_20241223
+ * command: FOUNDRY_PROFILE=arbitrum forge test --match-path=src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.t.sol -vv
+ */
+contract AaveV3Arbitrum_GHOBaseLaunch_20241223_Base is ProtocolV3TestBase {
+ struct CCIPSendParams {
+ address sender;
+ uint256 amount;
+ uint64 destChainSelector;
+ }
+
+ uint64 internal constant ARB_CHAIN_SELECTOR = CCIPUtils.ARB_CHAIN_SELECTOR;
+ uint64 internal constant BASE_CHAIN_SELECTOR = CCIPUtils.BASE_CHAIN_SELECTOR;
+ uint64 internal constant ETH_CHAIN_SELECTOR = CCIPUtils.ETH_CHAIN_SELECTOR;
+ uint256 internal constant CCIP_RATE_LIMIT_CAPACITY = 300_000e18;
+ uint256 internal constant CCIP_RATE_LIMIT_REFILL_RATE = 60e18;
+
+ IGhoToken internal constant GHO = IGhoToken(AaveV3ArbitrumAssets.GHO_UNDERLYING);
+ ITokenAdminRegistry internal constant TOKEN_ADMIN_REGISTRY =
+ ITokenAdminRegistry(0x39AE1032cF4B334a1Ed41cdD0833bdD7c7E7751E);
+ IEVM2EVMOnRamp internal constant ETH_ON_RAMP =
+ IEVM2EVMOnRamp(0x67761742ac8A21Ec4D76CA18cbd701e5A6F3Bef3);
+ IEVM2EVMOnRamp internal constant BASE_ON_RAMP =
+ IEVM2EVMOnRamp(0xc1b6287A3292d6469F2D8545877E40A2f75CA9a6);
+ IEVM2EVMOffRamp_1_5 internal constant ETH_OFF_RAMP =
+ IEVM2EVMOffRamp_1_5(0x91e46cc5590A4B9182e47f40006140A7077Dec31);
+ IEVM2EVMOffRamp_1_5 internal constant BASE_OFF_RAMP =
+ IEVM2EVMOffRamp_1_5(0xb62178f8198905D0Fa6d640Bdb188E4E8143Ac4b);
+
+ address internal constant RISK_COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60;
+ address public constant NEW_REMOTE_TOKEN_BASE = 0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee;
+ IRouter internal constant ROUTER = IRouter(0x141fa059441E0ca23ce184B6A78bafD2A517DdE8);
+ IGhoCcipSteward internal constant NEW_GHO_CCIP_STEWARD =
+ IGhoCcipSteward(0xCd5ab470AaC5c13e1063ee700503f3346b7C90Db);
+ IUpgradeableBurnMintTokenPool_1_5_1 internal constant NEW_TOKEN_POOL =
+ IUpgradeableBurnMintTokenPool_1_5_1(0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB);
+ address internal constant NEW_REMOTE_POOL_ETH = 0x06179f7C1be40863405f374E7f5F8806c728660A;
+ address internal constant NEW_REMOTE_POOL_BASE = 0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34;
+
+ AaveV3Arbitrum_GHOBaseLaunch_20241223 internal proposal;
+
+ address internal alice = makeAddr('alice');
+ address internal bob = makeAddr('bob');
+ address internal carol = makeAddr('carol');
+
+ event Burned(address indexed sender, uint256 amount);
+ event Minted(address indexed sender, address indexed recipient, uint256 amount);
+ event CCIPSendRequested(IInternal.EVM2EVMMessage message);
+
+ error CallerIsNotARampOnRouter(address);
+ error InvalidSourcePoolAddress(bytes);
+
+ function setUp() public virtual {
+ vm.createSelectFork(vm.rpcUrl('arbitrum'), 298375852);
+
+ // pre-requisite, to be removed after execution
+ executePayload(vm, address(new AaveV3Arbitrum_GHOCCIP151Upgrade_20241209()));
+
+ proposal = new AaveV3Arbitrum_GHOBaseLaunch_20241223();
+
+ _validateConstants();
+ }
+
+ function _validateConstants() private view {
+ assertEq(proposal.BASE_CHAIN_SELECTOR(), BASE_CHAIN_SELECTOR);
+ assertEq(address(proposal.TOKEN_POOL()), address(NEW_TOKEN_POOL));
+ assertEq(proposal.REMOTE_TOKEN_POOL_BASE(), NEW_REMOTE_POOL_BASE);
+ assertEq(proposal.REMOTE_GHO_TOKEN_BASE(), NEW_REMOTE_TOKEN_BASE);
+ assertEq(proposal.CCIP_RATE_LIMIT_CAPACITY(), CCIP_RATE_LIMIT_CAPACITY);
+ assertEq(proposal.CCIP_RATE_LIMIT_REFILL_RATE(), CCIP_RATE_LIMIT_REFILL_RATE);
+
+ assertEq(TOKEN_ADMIN_REGISTRY.typeAndVersion(), 'TokenAdminRegistry 1.5.0');
+ assertEq(NEW_TOKEN_POOL.typeAndVersion(), 'BurnMintTokenPool 1.5.1');
+ assertEq(ROUTER.typeAndVersion(), 'Router 1.2.0');
+
+ _assertOnRamp(ETH_ON_RAMP, ARB_CHAIN_SELECTOR, ETH_CHAIN_SELECTOR, ROUTER);
+ _assertOnRamp(BASE_ON_RAMP, ARB_CHAIN_SELECTOR, BASE_CHAIN_SELECTOR, ROUTER);
+ _assertOffRamp(ETH_OFF_RAMP, ETH_CHAIN_SELECTOR, ARB_CHAIN_SELECTOR, ROUTER);
+ _assertOffRamp(BASE_OFF_RAMP, BASE_CHAIN_SELECTOR, ARB_CHAIN_SELECTOR, ROUTER);
+
+ assertEq(NEW_GHO_CCIP_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);
+ assertEq(NEW_GHO_CCIP_STEWARD.GHO_TOKEN(), AaveV3ArbitrumAssets.GHO_UNDERLYING);
+ assertEq(NEW_GHO_CCIP_STEWARD.GHO_TOKEN_POOL(), address(NEW_TOKEN_POOL));
+ assertFalse(NEW_GHO_CCIP_STEWARD.BRIDGE_LIMIT_ENABLED());
+
+ ProxyAdmin newProxyAdmin = ProxyAdmin(proposal.NEW_GHO_TOKEN_PROXY_ADMIN());
+ assertEq(newProxyAdmin.owner(), GovernanceV3Arbitrum.EXECUTOR_LVL_1);
+ assertEq(newProxyAdmin.UPGRADE_INTERFACE_VERSION(), '5.0.0');
+ }
+
+ function _assertOnRamp(
+ IEVM2EVMOnRamp onRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(onRamp.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0');
+ assertEq(onRamp.getStaticConfig().chainSelector, srcSelector);
+ assertEq(onRamp.getStaticConfig().destChainSelector, dstSelector);
+ assertEq(onRamp.getDynamicConfig().router, address(router));
+ assertEq(router.getOnRamp(dstSelector), address(onRamp));
+ }
+
+ function _assertOffRamp(
+ IEVM2EVMOffRamp_1_5 offRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(offRamp.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0');
+ assertEq(offRamp.getStaticConfig().sourceChainSelector, srcSelector);
+ assertEq(offRamp.getStaticConfig().chainSelector, dstSelector);
+ assertEq(offRamp.getDynamicConfig().router, address(router));
+ assertTrue(router.isOffRamp(srcSelector, address(offRamp)));
+ }
+
+ function _getTokenMessage(
+ CCIPSendParams memory params
+ ) internal returns (IClient.EVM2AnyMessage memory, IInternal.EVM2EVMMessage memory) {
+ IClient.EVM2AnyMessage memory message = CCIPUtils.generateMessage(params.sender, 1);
+ message.tokenAmounts[0] = IClient.EVMTokenAmount({token: address(GHO), amount: params.amount});
+
+ uint256 feeAmount = ROUTER.getFee(params.destChainSelector, message);
+ deal(params.sender, feeAmount);
+
+ IInternal.EVM2EVMMessage memory eventArg = CCIPUtils.messageToEvent(
+ CCIPUtils.MessageToEventParams({
+ message: message,
+ router: ROUTER,
+ sourceChainSelector: ARB_CHAIN_SELECTOR,
+ destChainSelector: params.destChainSelector,
+ feeTokenAmount: feeAmount,
+ originalSender: params.sender,
+ sourceToken: address(GHO),
+ destinationToken: address(
+ params.destChainSelector == BASE_CHAIN_SELECTOR
+ ? NEW_REMOTE_TOKEN_BASE
+ : AaveV3EthereumAssets.GHO_UNDERLYING
+ )
+ })
+ );
+
+ return (message, eventArg);
+ }
+
+ function _tokenBucketToConfig(
+ IRateLimiter.TokenBucket memory bucket
+ ) internal pure returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: bucket.isEnabled,
+ capacity: bucket.capacity,
+ rate: bucket.rate
+ });
+ }
+
+ function _getDisabledConfig() internal pure returns (IRateLimiter.Config memory) {
+ return IRateLimiter.Config({isEnabled: false, capacity: 0, rate: 0});
+ }
+
+ function _getImplementation(address proxy) internal view returns (address) {
+ bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
+ return address(uint160(uint256(vm.load(proxy, slot))));
+ }
+
+ function _getProxyAdmin(address proxy) internal view returns (address) {
+ bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1);
+ return address(uint160(uint256(vm.load(proxy, slot))));
+ }
+
+ function _readInitialized(address proxy) internal view returns (uint8) {
+ return uint8(uint256(vm.load(proxy, bytes32(0))));
+ }
+
+ function _getRateLimiterConfig() internal view returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: true,
+ capacity: proposal.CCIP_RATE_LIMIT_CAPACITY(),
+ rate: proposal.CCIP_RATE_LIMIT_REFILL_RATE()
+ });
+ }
+
+ function _getOutboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return (amount / CCIP_RATE_LIMIT_REFILL_RATE) + 1; // account for rounding
+ }
+
+ function _getInboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return amount / CCIP_RATE_LIMIT_REFILL_RATE + 1; // account for rounding
+ }
+
+ function _min(uint256 a, uint256 b) internal pure returns (uint256) {
+ return a < b ? a : b;
+ }
+
+ function assertEq(
+ IRateLimiter.TokenBucket memory bucket,
+ IRateLimiter.Config memory config
+ ) internal pure {
+ assertEq(bucket.isEnabled, config.isEnabled);
+ assertEq(bucket.capacity, config.capacity);
+ assertEq(bucket.rate, config.rate);
+ }
+}
+
+contract AaveV3Arbitrum_GHOBaseLaunch_20241223_PreExecution is
+ AaveV3Arbitrum_GHOBaseLaunch_20241223_Base
+{
+ function test_ghoTokenProxyAdminUpgrade() public {
+ assertEq(_getProxyAdmin(address(GHO)), MiscArbitrum.PROXY_ADMIN);
+ executePayload(vm, address(proposal));
+ assertEq(_getProxyAdmin(address(GHO)), proposal.NEW_GHO_TOKEN_PROXY_ADMIN());
+ }
+
+ function test_ghoProxyAdminCanUpgradeImplementation() public {
+ executePayload(vm, address(proposal));
+ address miscImpl = makeAddr('miscImpl');
+ vm.etch(miscImpl, hex'00'); // stop opcode
+ vm.startPrank(GovernanceV3Arbitrum.EXECUTOR_LVL_1);
+ ProxyAdmin(proposal.NEW_GHO_TOKEN_PROXY_ADMIN()).upgradeAndCall(
+ ITransparentUpgradeableProxy(address(GHO)),
+ miscImpl,
+ ''
+ );
+ assertEq(_getImplementation(address(GHO)), miscImpl);
+ }
+}
+
+contract AaveV3Arbitrum_GHOBaseLaunch_20241223_PostExecution is
+ AaveV3Arbitrum_GHOBaseLaunch_20241223_Base
+{
+ function setUp() public override {
+ super.setUp();
+ executePayload(vm, address(proposal));
+ }
+
+ function test_basePoolConfig() public view {
+ assertEq(NEW_TOKEN_POOL.getSupportedChains().length, 2);
+ assertEq(NEW_TOKEN_POOL.getSupportedChains()[0], ETH_CHAIN_SELECTOR);
+ assertEq(NEW_TOKEN_POOL.getSupportedChains()[1], BASE_CHAIN_SELECTOR);
+ assertEq(
+ NEW_TOKEN_POOL.getRemoteToken(ETH_CHAIN_SELECTOR),
+ abi.encode(address(AaveV3EthereumAssets.GHO_UNDERLYING))
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getRemoteToken(BASE_CHAIN_SELECTOR),
+ abi.encode(address(NEW_REMOTE_TOKEN_BASE))
+ );
+ assertEq(NEW_TOKEN_POOL.getRemotePools(BASE_CHAIN_SELECTOR).length, 1);
+ assertEq(
+ NEW_TOKEN_POOL.getRemotePools(BASE_CHAIN_SELECTOR)[0],
+ abi.encode(address(NEW_REMOTE_POOL_BASE))
+ );
+ assertEq(NEW_TOKEN_POOL.getRemotePools(ETH_CHAIN_SELECTOR).length, 2);
+ assertEq(
+ NEW_TOKEN_POOL.getRemotePools(ETH_CHAIN_SELECTOR)[1], // 0th is the 1.4 token pool
+ abi.encode(address(NEW_REMOTE_POOL_ETH))
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(ETH_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(ETH_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(BASE_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(BASE_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ }
+
+ function test_sendMessageToBaseSucceeds(uint256 amount) public {
+ uint256 bridgeableAmount = _min(
+ GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel,
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getOutboundRefillTime(amount)); // wait for the rate limiter to refill
+
+ deal(address(GHO), alice, amount);
+ vm.prank(alice);
+ GHO.approve(address(ROUTER), amount);
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel;
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({amount: amount, sender: alice, destChainSelector: BASE_CHAIN_SELECTOR})
+ );
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Burned(address(BASE_ON_RAMP), amount);
+ vm.expectEmit(address(BASE_ON_RAMP));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ ROUTER.ccipSend{value: eventArg.feeTokenAmount}(BASE_CHAIN_SELECTOR, message);
+
+ assertEq(GHO.balanceOf(alice), aliceBalance - amount);
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel - amount);
+ }
+
+ function test_sendMessageToEthSucceeds(uint256 amount) public {
+ uint256 bridgeableAmount = _min(
+ GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel,
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getOutboundRefillTime(amount)); // wait for the rate limiter to refill
+
+ deal(address(GHO), alice, amount);
+ vm.prank(alice);
+ GHO.approve(address(ROUTER), amount);
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel;
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({amount: amount, sender: alice, destChainSelector: ETH_CHAIN_SELECTOR})
+ );
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Burned(address(ETH_ON_RAMP), amount);
+ vm.expectEmit(address(ETH_ON_RAMP));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ ROUTER.ccipSend{value: eventArg.feeTokenAmount}(ETH_CHAIN_SELECTOR, message);
+
+ assertEq(GHO.balanceOf(alice), aliceBalance - amount);
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel - amount);
+ }
+
+ function test_offRampViaBaseSucceeds(uint256 amount) public {
+ (uint256 bucketCapacity, uint256 bucketLevel) = GHO.getFacilitatorBucket(
+ address(NEW_TOKEN_POOL)
+ );
+ uint256 mintAbleAmount = _min(bucketCapacity - bucketLevel, CCIP_RATE_LIMIT_CAPACITY);
+ amount = bound(amount, 1, mintAbleAmount);
+ skip(_getInboundRefillTime(amount));
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Minted(address(BASE_OFF_RAMP), alice, amount);
+
+ vm.prank(address(BASE_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: BASE_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_BASE)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel + amount);
+ assertEq(GHO.balanceOf(alice), aliceBalance + amount);
+ }
+
+ function test_offRampViaEthSucceeds(uint256 amount) public {
+ (uint256 bucketCapacity, uint256 bucketLevel) = GHO.getFacilitatorBucket(
+ address(NEW_TOKEN_POOL)
+ );
+ uint256 mintAbleAmount = _min(bucketCapacity - bucketLevel, CCIP_RATE_LIMIT_CAPACITY);
+ amount = bound(amount, 1, mintAbleAmount);
+ skip(_getInboundRefillTime(amount));
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Minted(address(ETH_OFF_RAMP), alice, amount);
+
+ vm.prank(address(ETH_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ETH_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ETH)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel + amount);
+ assertEq(GHO.balanceOf(alice), aliceBalance + amount);
+ }
+
+ function test_cannotUseBaseOffRampForEthMessages() public {
+ uint256 amount = 100e18;
+ skip(_getInboundRefillTime(amount));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(CallerIsNotARampOnRouter.selector, address(BASE_OFF_RAMP))
+ );
+ vm.prank(address(BASE_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ETH_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ETH)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+ }
+
+ function test_cannotOffRampOtherChainMessages() public {
+ uint256 amount = 100e18;
+ skip(_getInboundRefillTime(amount));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ InvalidSourcePoolAddress.selector,
+ abi.encode(address(NEW_REMOTE_POOL_ETH))
+ )
+ );
+ vm.prank(address(BASE_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: BASE_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ETH)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ InvalidSourcePoolAddress.selector,
+ abi.encode(address(NEW_REMOTE_POOL_BASE))
+ )
+ );
+ vm.prank(address(ETH_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ETH_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_BASE)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.sol
new file mode 100644
index 000000000..929ba2a3c
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.sol
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol';
+import {IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol';
+import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+import {IGhoBucketSteward} from 'src/interfaces/IGhoBucketSteward.sol';
+import {IGhoToken} from 'src/interfaces/IGhoToken.sol';
+
+import {AaveV3Base} from 'aave-address-book/AaveV3Base.sol';
+import {GovernanceV3Base} from 'aave-address-book/GovernanceV3Base.sol';
+import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+
+/**
+ * @title GHO Base Launch
+ * @author Aave Labs
+ * - Discussion: https://governance.aave.com/t/arfc-launch-gho-on-base-set-aci-as-emissions-manager-for-rewards/19338
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x03dc21e0423c60082dc23317af6ebaf997610cbc2cbb0f5a385653bd99a524fe
+ */
+contract AaveV3Base_GHOBaseLaunch_20241223 is IProposalGenericExecutor {
+ uint64 public constant ETH_CHAIN_SELECTOR = 5009297550715157269;
+ uint64 public constant ARB_CHAIN_SELECTOR = 4949039107694359620;
+
+ uint128 public constant CCIP_BUCKET_CAPACITY = 20_000_000e18; // 20M GHO
+
+ // https://basescan.org/address/0x6f6C373d09C07425BaAE72317863d7F6bb731e37
+ ITokenAdminRegistry public constant TOKEN_ADMIN_REGISTRY =
+ ITokenAdminRegistry(0x6f6C373d09C07425BaAE72317863d7F6bb731e37);
+ // https://basescan.org/address/0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34
+ IUpgradeableBurnMintTokenPool_1_5_1 public constant TOKEN_POOL =
+ IUpgradeableBurnMintTokenPool_1_5_1(0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34);
+
+ // https://basescan.org/address/0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee
+ IGhoToken public constant GHO_TOKEN = IGhoToken(0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee);
+
+ // https://basescan.org/address/0xC5BcC58BE6172769ca1a78B8A45752E3C5059c39
+ address public constant GHO_AAVE_STEWARD = 0xC5BcC58BE6172769ca1a78B8A45752E3C5059c39;
+ // https://basescan.org/address/0x3c47237479e7569653eF9beC4a7Cd2ee3F78b396
+ address public constant GHO_BUCKET_STEWARD = 0x3c47237479e7569653eF9beC4a7Cd2ee3F78b396;
+ // https://basescan.org/address/0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB
+ address public constant GHO_CCIP_STEWARD = 0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB;
+
+ // https://etherscan.io/address/0x06179f7C1be40863405f374E7f5F8806c728660A
+ address public constant REMOTE_TOKEN_POOL_ETH = 0x06179f7C1be40863405f374E7f5F8806c728660A;
+ // https://arbiscan.io/address/0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB
+ address public constant REMOTE_TOKEN_POOL_ARB = 0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB;
+
+ // Token Rate Limit Capacity: 300_000 GHO
+ uint128 public constant CCIP_RATE_LIMIT_CAPACITY = 300_000e18;
+ // Token Rate Limit Refill Rate: 60 GHO per second (=> 216_000 GHO per hour)
+ uint128 public constant CCIP_RATE_LIMIT_REFILL_RATE = 60e18;
+
+ function execute() external {
+ _acceptOwnership();
+ _setupStewardsAndTokenPoolOnGho();
+ _setupRemoteAndRegisterTokenPool();
+ }
+
+ function _acceptOwnership() internal {
+ TOKEN_ADMIN_REGISTRY.acceptAdminRole(address(GHO_TOKEN));
+ TOKEN_POOL.acceptOwnership();
+ }
+
+ function _setupStewardsAndTokenPoolOnGho() internal {
+ GHO_TOKEN.grantRole(GHO_TOKEN.FACILITATOR_MANAGER_ROLE(), GovernanceV3Base.EXECUTOR_LVL_1);
+ GHO_TOKEN.grantRole(GHO_TOKEN.BUCKET_MANAGER_ROLE(), GovernanceV3Base.EXECUTOR_LVL_1);
+
+ // Token Pool as facilitator with 20M GHO capacity
+ GHO_TOKEN.addFacilitator(address(TOKEN_POOL), 'CCIP TokenPool v1.5.1', CCIP_BUCKET_CAPACITY);
+
+ // Gho Aave Steward
+ AaveV3Base.ACL_MANAGER.grantRole(AaveV3Base.ACL_MANAGER.RISK_ADMIN_ROLE(), GHO_AAVE_STEWARD);
+
+ // Gho Bucket Steward
+ GHO_TOKEN.grantRole(GHO_TOKEN.BUCKET_MANAGER_ROLE(), GHO_BUCKET_STEWARD);
+ address[] memory facilitatorList = new address[](1);
+ facilitatorList[0] = address(TOKEN_POOL);
+ IGhoBucketSteward(GHO_BUCKET_STEWARD).setControlledFacilitator({
+ facilitatorList: facilitatorList,
+ approve: true
+ });
+
+ // Gho CCIP Steward
+ TOKEN_POOL.setRateLimitAdmin(GHO_CCIP_STEWARD);
+ }
+
+ function _setupRemoteAndRegisterTokenPool() internal {
+ IRateLimiter.Config memory rateLimiterConfig = IRateLimiter.Config({
+ isEnabled: true,
+ capacity: CCIP_RATE_LIMIT_CAPACITY,
+ rate: CCIP_RATE_LIMIT_REFILL_RATE
+ });
+
+ IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate[]
+ memory chainsToAdd = new IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate[](2);
+
+ {
+ bytes[] memory remotePoolAddresses = new bytes[](1);
+ remotePoolAddresses[0] = abi.encode(REMOTE_TOKEN_POOL_ETH);
+ chainsToAdd[0] = IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate({
+ remoteChainSelector: ETH_CHAIN_SELECTOR,
+ remotePoolAddresses: remotePoolAddresses,
+ remoteTokenAddress: abi.encode(AaveV3EthereumAssets.GHO_UNDERLYING),
+ outboundRateLimiterConfig: rateLimiterConfig,
+ inboundRateLimiterConfig: rateLimiterConfig
+ });
+ }
+
+ {
+ bytes[] memory remotePoolAddresses = new bytes[](1);
+ remotePoolAddresses[0] = abi.encode(REMOTE_TOKEN_POOL_ARB);
+ chainsToAdd[1] = IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate({
+ remoteChainSelector: ARB_CHAIN_SELECTOR,
+ remotePoolAddresses: remotePoolAddresses,
+ remoteTokenAddress: abi.encode(AaveV3ArbitrumAssets.GHO_UNDERLYING),
+ outboundRateLimiterConfig: rateLimiterConfig,
+ inboundRateLimiterConfig: rateLimiterConfig
+ });
+ }
+
+ // setup remote token pools
+ TOKEN_POOL.applyChainUpdates({
+ remoteChainSelectorsToRemove: new uint64[](0),
+ chainsToAdd: chainsToAdd
+ });
+
+ // register token pool
+ TOKEN_ADMIN_REGISTRY.setPool(address(GHO_TOKEN), address(TOKEN_POOL));
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.t.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.t.sol
new file mode 100644
index 000000000..22edf8bdd
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.t.sol
@@ -0,0 +1,577 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import 'forge-std/Test.sol';
+
+import {IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol';
+import {IPool as IPool_CCIP} from 'src/interfaces/ccip/tokenPool/IPool.sol';
+import {IClient} from 'src/interfaces/ccip/IClient.sol';
+import {IInternal} from 'src/interfaces/ccip/IInternal.sol';
+import {IRouter} from 'src/interfaces/ccip/IRouter.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol';
+import {IEVM2EVMOffRamp_1_5} from 'src/interfaces/ccip/IEVM2EVMOffRamp.sol';
+import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol';
+import {IGhoToken} from 'src/interfaces/IGhoToken.sol';
+import {IGhoAaveSteward} from 'src/interfaces/IGhoAaveSteward.sol';
+import {IGhoBucketSteward} from 'src/interfaces/IGhoBucketSteward.sol';
+import {IGhoCcipSteward} from 'src/interfaces/IGhoCcipSteward.sol';
+import {IOwnable} from 'aave-address-book/common/IOwnable.sol';
+
+import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3Base} from 'aave-address-book/AaveV3Base.sol';
+import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {GovernanceV3Base} from 'aave-address-book/GovernanceV3Base.sol';
+
+import {ProxyAdmin} from 'solidity-utils/contracts/transparent-proxy/ProxyAdmin.sol';
+
+import {CCIPUtils} from './utils/CCIPUtils.sol';
+
+import {AaveV3Base_GHOBaseLaunch_20241223} from './AaveV3Base_GHOBaseLaunch_20241223.sol';
+
+/**
+ * @dev Test for AaveV3Base_GHOBaseLaunch_20241223
+ * command: FOUNDRY_PROFILE=base forge test --match-path=src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.t.sol -vv
+ */
+contract AaveV3Base_GHOBaseLaunch_20241223_Base is ProtocolV3TestBase {
+ struct CCIPSendParams {
+ address sender;
+ uint256 amount;
+ uint64 destChainSelector;
+ }
+
+ uint64 internal constant ARB_CHAIN_SELECTOR = CCIPUtils.ARB_CHAIN_SELECTOR;
+ uint64 internal constant BASE_CHAIN_SELECTOR = CCIPUtils.BASE_CHAIN_SELECTOR;
+ uint64 internal constant ETH_CHAIN_SELECTOR = CCIPUtils.ETH_CHAIN_SELECTOR;
+ uint256 internal constant CCIP_RATE_LIMIT_CAPACITY = 300_000e18;
+ uint256 internal constant CCIP_RATE_LIMIT_REFILL_RATE = 60e18;
+
+ ITokenAdminRegistry internal constant TOKEN_ADMIN_REGISTRY =
+ ITokenAdminRegistry(0x6f6C373d09C07425BaAE72317863d7F6bb731e37);
+ IEVM2EVMOnRamp internal constant ARB_ON_RAMP =
+ IEVM2EVMOnRamp(0x9D0ffA76C7F82C34Be313b5bFc6d42A72dA8CA69);
+ IEVM2EVMOnRamp internal constant ETH_ON_RAMP =
+ IEVM2EVMOnRamp(0x56b30A0Dcd8dc87Ec08b80FA09502bAB801fa78e);
+
+ IEVM2EVMOffRamp_1_5 internal constant ARB_OFF_RAMP =
+ IEVM2EVMOffRamp_1_5(0x7D38c6363d5E4DFD500a691Bc34878b383F58d93);
+ IEVM2EVMOffRamp_1_5 internal constant ETH_OFF_RAMP =
+ IEVM2EVMOffRamp_1_5(0xCA04169671A81E4fB8768cfaD46c347ae65371F1);
+
+ IRouter internal constant ROUTER = IRouter(0x881e3A65B4d4a04dD529061dd0071cf975F58bCD);
+ address internal constant RMN_PROXY = 0xC842c69d54F83170C42C4d556B4F6B2ca53Dd3E8;
+
+ IGhoToken internal constant GHO = IGhoToken(0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee);
+ address internal constant RISK_COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60;
+
+ IGhoAaveSteward internal constant NEW_GHO_AAVE_STEWARD =
+ IGhoAaveSteward(0xC5BcC58BE6172769ca1a78B8A45752E3C5059c39);
+ IGhoBucketSteward internal constant NEW_GHO_BUCKET_STEWARD =
+ IGhoBucketSteward(0x3c47237479e7569653eF9beC4a7Cd2ee3F78b396);
+ IGhoCcipSteward internal constant NEW_GHO_CCIP_STEWARD =
+ IGhoCcipSteward(0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB);
+ IUpgradeableBurnMintTokenPool_1_5_1 internal constant NEW_TOKEN_POOL =
+ IUpgradeableBurnMintTokenPool_1_5_1(0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34);
+ address internal constant NEW_REMOTE_POOL_ARB = 0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB;
+ address internal constant NEW_REMOTE_POOL_ETH = 0x06179f7C1be40863405f374E7f5F8806c728660A;
+
+ AaveV3Base_GHOBaseLaunch_20241223 internal proposal;
+
+ address internal alice = makeAddr('alice');
+ address internal bob = makeAddr('bob');
+ address internal carol = makeAddr('carol');
+
+ event Burned(address indexed sender, uint256 amount);
+ event Minted(address indexed sender, address indexed recipient, uint256 amount);
+ event CCIPSendRequested(IInternal.EVM2EVMMessage message);
+
+ error CallerIsNotARampOnRouter(address);
+ error InvalidSourcePoolAddress(bytes);
+
+ function setUp() public virtual {
+ vm.createSelectFork(vm.rpcUrl('base'), 25415842);
+ proposal = new AaveV3Base_GHOBaseLaunch_20241223();
+
+ _performCcipPreReq();
+ _validateConstants();
+ }
+
+ function _performCcipPreReq() internal {
+ vm.prank(TOKEN_ADMIN_REGISTRY.owner());
+ TOKEN_ADMIN_REGISTRY.proposeAdministrator(address(GHO), GovernanceV3Base.EXECUTOR_LVL_1);
+ }
+
+ function _validateConstants() private view {
+ assertEq(proposal.ETH_CHAIN_SELECTOR(), ETH_CHAIN_SELECTOR);
+ assertEq(proposal.ARB_CHAIN_SELECTOR(), ARB_CHAIN_SELECTOR);
+ assertEq(proposal.CCIP_BUCKET_CAPACITY(), 20_000_000e18);
+ assertEq(address(proposal.TOKEN_ADMIN_REGISTRY()), address(TOKEN_ADMIN_REGISTRY));
+ assertEq(address(proposal.TOKEN_POOL()), address(NEW_TOKEN_POOL));
+ assertEq(address(proposal.GHO_TOKEN()), address(GHO));
+ assertEq(proposal.GHO_AAVE_STEWARD(), address(NEW_GHO_AAVE_STEWARD));
+ assertEq(proposal.GHO_BUCKET_STEWARD(), address(NEW_GHO_BUCKET_STEWARD));
+ assertEq(proposal.GHO_CCIP_STEWARD(), address(NEW_GHO_CCIP_STEWARD));
+ assertEq(proposal.REMOTE_TOKEN_POOL_ARB(), NEW_REMOTE_POOL_ARB);
+ assertEq(proposal.REMOTE_TOKEN_POOL_ETH(), NEW_REMOTE_POOL_ETH);
+ assertEq(proposal.CCIP_RATE_LIMIT_CAPACITY(), CCIP_RATE_LIMIT_CAPACITY);
+ assertEq(proposal.CCIP_RATE_LIMIT_REFILL_RATE(), CCIP_RATE_LIMIT_REFILL_RATE);
+
+ assertEq(TOKEN_ADMIN_REGISTRY.typeAndVersion(), 'TokenAdminRegistry 1.5.0');
+ assertEq(NEW_TOKEN_POOL.typeAndVersion(), 'BurnMintTokenPool 1.5.1');
+ assertEq(ROUTER.typeAndVersion(), 'Router 1.2.0');
+
+ _assertOnRamp(ARB_ON_RAMP, BASE_CHAIN_SELECTOR, ARB_CHAIN_SELECTOR, ROUTER);
+ _assertOnRamp(ETH_ON_RAMP, BASE_CHAIN_SELECTOR, ETH_CHAIN_SELECTOR, ROUTER);
+ _assertOffRamp(ARB_OFF_RAMP, ARB_CHAIN_SELECTOR, BASE_CHAIN_SELECTOR, ROUTER);
+ _assertOffRamp(ETH_OFF_RAMP, ETH_CHAIN_SELECTOR, BASE_CHAIN_SELECTOR, ROUTER);
+
+ assertEq(_getProxyAdmin(address(GHO)).UPGRADE_INTERFACE_VERSION(), '5.0.0');
+ assertEq(_getProxyAdmin(address(NEW_TOKEN_POOL)).UPGRADE_INTERFACE_VERSION(), '5.0.0');
+ }
+
+ function _assertOnRamp(
+ IEVM2EVMOnRamp onRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(onRamp.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0');
+ assertEq(onRamp.getStaticConfig().chainSelector, srcSelector);
+ assertEq(onRamp.getStaticConfig().destChainSelector, dstSelector);
+ assertEq(onRamp.getDynamicConfig().router, address(router));
+ assertEq(router.getOnRamp(dstSelector), address(onRamp));
+ }
+
+ function _assertOffRamp(
+ IEVM2EVMOffRamp_1_5 offRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(offRamp.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0');
+ assertEq(offRamp.getStaticConfig().sourceChainSelector, srcSelector);
+ assertEq(offRamp.getStaticConfig().chainSelector, dstSelector);
+ assertEq(offRamp.getDynamicConfig().router, address(router));
+ assertTrue(router.isOffRamp(srcSelector, address(offRamp)));
+ }
+
+ function _getTokenMessage(
+ CCIPSendParams memory params
+ ) internal returns (IClient.EVM2AnyMessage memory, IInternal.EVM2EVMMessage memory) {
+ IClient.EVM2AnyMessage memory message = CCIPUtils.generateMessage(params.sender, 1);
+ message.tokenAmounts[0] = IClient.EVMTokenAmount({token: address(GHO), amount: params.amount});
+
+ uint256 feeAmount = ROUTER.getFee(params.destChainSelector, message);
+ deal(params.sender, feeAmount);
+
+ IInternal.EVM2EVMMessage memory eventArg = CCIPUtils.messageToEvent(
+ CCIPUtils.MessageToEventParams({
+ message: message,
+ router: ROUTER,
+ sourceChainSelector: BASE_CHAIN_SELECTOR,
+ destChainSelector: params.destChainSelector,
+ feeTokenAmount: feeAmount,
+ originalSender: params.sender,
+ sourceToken: address(GHO),
+ destinationToken: address(
+ params.destChainSelector == ARB_CHAIN_SELECTOR
+ ? AaveV3ArbitrumAssets.GHO_UNDERLYING
+ : AaveV3EthereumAssets.GHO_UNDERLYING
+ )
+ })
+ );
+
+ return (message, eventArg);
+ }
+
+ function _tokenBucketToConfig(
+ IRateLimiter.TokenBucket memory bucket
+ ) internal pure returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: bucket.isEnabled,
+ capacity: bucket.capacity,
+ rate: bucket.rate
+ });
+ }
+
+ function _getDisabledConfig() internal pure returns (IRateLimiter.Config memory) {
+ return IRateLimiter.Config({isEnabled: false, capacity: 0, rate: 0});
+ }
+
+ function _getImplementation(address proxy) internal view returns (address) {
+ bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
+ return address(uint160(uint256(vm.load(proxy, slot))));
+ }
+
+ function _getProxyAdmin(address proxy) internal view returns (ProxyAdmin) {
+ bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1);
+ return ProxyAdmin(address(uint160(uint256(vm.load(proxy, slot)))));
+ }
+
+ function _readInitialized(address proxy) internal view returns (uint8) {
+ return uint8(uint256(vm.load(proxy, bytes32(0))));
+ }
+
+ function _getRateLimiterConfig() internal view returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: true,
+ capacity: proposal.CCIP_RATE_LIMIT_CAPACITY(),
+ rate: proposal.CCIP_RATE_LIMIT_REFILL_RATE()
+ });
+ }
+
+ function _getOutboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return (amount / CCIP_RATE_LIMIT_REFILL_RATE) + 1; // account for rounding
+ }
+
+ function _getInboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return amount / CCIP_RATE_LIMIT_REFILL_RATE + 1; // account for rounding
+ }
+
+ function _min(uint256 a, uint256 b) internal pure returns (uint256) {
+ return a < b ? a : b;
+ }
+
+ function assertEq(
+ IRateLimiter.TokenBucket memory bucket,
+ IRateLimiter.Config memory config
+ ) internal pure {
+ assertEq(bucket.isEnabled, config.isEnabled);
+ assertEq(bucket.capacity, config.capacity);
+ assertEq(bucket.rate, config.rate);
+ }
+}
+
+contract AaveV3Base_GHOBaseLaunch_20241223_PreExecution is AaveV3Base_GHOBaseLaunch_20241223_Base {
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest('AaveV3Base_GHOBaseLaunch_20241223', AaveV3Base.POOL, address(proposal));
+ }
+
+ function test_stewardRoles() public {
+ // gho token is deployed in the AIP, does not existing before
+
+ assertFalse(
+ AaveV3Base.ACL_MANAGER.hasRole(
+ AaveV3Base.ACL_MANAGER.RISK_ADMIN_ROLE(),
+ address(NEW_GHO_AAVE_STEWARD)
+ )
+ );
+ assertEq(NEW_GHO_BUCKET_STEWARD.getControlledFacilitators().length, 0);
+ assertEq(NEW_TOKEN_POOL.getRateLimitAdmin(), address(0));
+
+ executePayload(vm, address(proposal));
+
+ assertTrue(GHO.hasRole(GHO.FACILITATOR_MANAGER_ROLE(), GovernanceV3Base.EXECUTOR_LVL_1));
+ assertTrue(GHO.hasRole(GHO.BUCKET_MANAGER_ROLE(), GovernanceV3Base.EXECUTOR_LVL_1));
+
+ IGhoToken.Facilitator memory facilitator = GHO.getFacilitator(address(NEW_TOKEN_POOL));
+ assertEq(facilitator.label, 'CCIP TokenPool v1.5.1');
+ assertEq(facilitator.bucketLevel, 0);
+ assertEq(facilitator.bucketCapacity, proposal.CCIP_BUCKET_CAPACITY());
+
+ assertTrue(
+ AaveV3Base.ACL_MANAGER.hasRole(
+ AaveV3Base.ACL_MANAGER.RISK_ADMIN_ROLE(),
+ address(NEW_GHO_AAVE_STEWARD)
+ )
+ );
+
+ assertTrue(GHO.hasRole(GHO.BUCKET_MANAGER_ROLE(), address(NEW_GHO_BUCKET_STEWARD)));
+
+ address[] memory facilitatorList = NEW_GHO_BUCKET_STEWARD.getControlledFacilitators();
+ assertEq(facilitatorList.length, 1);
+ assertEq(facilitatorList[0], address(NEW_TOKEN_POOL));
+ assertTrue(NEW_GHO_BUCKET_STEWARD.isControlledFacilitator(address(NEW_TOKEN_POOL)));
+
+ assertEq(NEW_TOKEN_POOL.getRateLimitAdmin(), address(NEW_GHO_CCIP_STEWARD));
+ }
+
+ function test_stewardsConfig() public view {
+ assertEq(IOwnable(address(NEW_GHO_AAVE_STEWARD)).owner(), GovernanceV3Base.EXECUTOR_LVL_1);
+ assertEq(
+ NEW_GHO_AAVE_STEWARD.POOL_ADDRESSES_PROVIDER(),
+ address(AaveV3Base.POOL_ADDRESSES_PROVIDER)
+ );
+ assertEq(
+ NEW_GHO_AAVE_STEWARD.POOL_DATA_PROVIDER(),
+ address(AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER)
+ );
+ assertEq(NEW_GHO_AAVE_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);
+ IGhoAaveSteward.BorrowRateConfig memory config = NEW_GHO_AAVE_STEWARD.getBorrowRateConfig();
+ assertEq(config.optimalUsageRatioMaxChange, 500);
+ assertEq(config.baseVariableBorrowRateMaxChange, 500);
+ assertEq(config.variableRateSlope1MaxChange, 500);
+ assertEq(config.variableRateSlope2MaxChange, 500);
+
+ assertEq(IOwnable(address(NEW_GHO_BUCKET_STEWARD)).owner(), GovernanceV3Base.EXECUTOR_LVL_1);
+ assertEq(NEW_GHO_BUCKET_STEWARD.GHO_TOKEN(), address(GHO));
+ assertEq(NEW_GHO_BUCKET_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);
+ assertEq(NEW_GHO_BUCKET_STEWARD.getControlledFacilitators().length, 0); // before AIP, no controlled facilitators are set
+
+ assertEq(NEW_GHO_CCIP_STEWARD.GHO_TOKEN(), address(GHO));
+ assertEq(NEW_GHO_CCIP_STEWARD.GHO_TOKEN_POOL(), address(NEW_TOKEN_POOL));
+ assertEq(NEW_GHO_CCIP_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);
+ assertFalse(NEW_GHO_CCIP_STEWARD.BRIDGE_LIMIT_ENABLED());
+ assertEq(
+ abi.encode(NEW_GHO_CCIP_STEWARD.getCcipTimelocks()),
+ abi.encode(IGhoCcipSteward.CcipDebounce(0, 0))
+ );
+ }
+
+ function test_newTokenPoolInitialization() public {
+ vm.expectRevert('Initializable: contract is already initialized');
+ NEW_TOKEN_POOL.initialize(makeAddr('owner'), new address[](0), makeAddr('router'));
+ assertEq(_readInitialized(address(NEW_TOKEN_POOL)), 1);
+
+ address IMPL = _getImplementation(address(NEW_TOKEN_POOL));
+ vm.expectRevert('Initializable: contract is already initialized');
+ IUpgradeableBurnMintTokenPool_1_5_1(IMPL).initialize(
+ makeAddr('owner'),
+ new address[](0),
+ makeAddr('router')
+ );
+ assertEq(_readInitialized(IMPL), 255);
+ }
+
+ function test_tokenPoolConfig() public {
+ executePayload(vm, address(proposal));
+
+ assertEq(NEW_TOKEN_POOL.owner(), GovernanceV3Base.EXECUTOR_LVL_1);
+ assertEq(
+ address(uint160(uint256(vm.load(address(NEW_TOKEN_POOL), bytes32(0))) >> 2)), // pending owner
+ address(0)
+ );
+ assertEq(NEW_TOKEN_POOL.getToken(), address(GHO));
+ assertEq(NEW_TOKEN_POOL.getTokenDecimals(), GHO.decimals());
+ assertEq(NEW_TOKEN_POOL.getRmnProxy(), RMN_PROXY);
+ assertFalse(NEW_TOKEN_POOL.getAllowListEnabled());
+ assertEq(abi.encode(NEW_TOKEN_POOL.getAllowList()), abi.encode(new address[](0)));
+ assertEq(NEW_TOKEN_POOL.getRouter(), address(ROUTER));
+
+ assertEq(NEW_TOKEN_POOL.getSupportedChains().length, 2);
+ assertEq(NEW_TOKEN_POOL.getSupportedChains()[0], ETH_CHAIN_SELECTOR);
+ assertEq(NEW_TOKEN_POOL.getSupportedChains()[1], ARB_CHAIN_SELECTOR);
+ assertEq(
+ NEW_TOKEN_POOL.getRemoteToken(ETH_CHAIN_SELECTOR),
+ abi.encode(AaveV3EthereumAssets.GHO_UNDERLYING)
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getRemoteToken(ARB_CHAIN_SELECTOR),
+ abi.encode(AaveV3ArbitrumAssets.GHO_UNDERLYING)
+ );
+ assertEq(NEW_TOKEN_POOL.getRemotePools(ETH_CHAIN_SELECTOR).length, 1);
+ assertEq(NEW_TOKEN_POOL.getRemotePools(ETH_CHAIN_SELECTOR)[0], abi.encode(NEW_REMOTE_POOL_ETH));
+ assertEq(NEW_TOKEN_POOL.getRemotePools(ARB_CHAIN_SELECTOR).length, 1);
+ assertEq(NEW_TOKEN_POOL.getRemotePools(ARB_CHAIN_SELECTOR)[0], abi.encode(NEW_REMOTE_POOL_ARB));
+
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(ETH_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(ETH_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(ARB_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(ARB_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ }
+}
+
+contract AaveV3Base_GHOBaseLaunch_20241223_PostExecution is AaveV3Base_GHOBaseLaunch_20241223_Base {
+ function setUp() public override {
+ super.setUp();
+
+ executePayload(vm, address(proposal));
+ }
+
+ function test_sendMessageToArbSucceeds(uint256 amount) public {
+ amount = bound(amount, 1, CCIP_RATE_LIMIT_CAPACITY);
+ skip(_getOutboundRefillTime(amount)); // wait for the rate limiter to refill
+ // mock previously bridged amount
+ vm.prank(address(NEW_TOKEN_POOL));
+ GHO.mint(alice, amount); // increase bucket level
+
+ vm.prank(alice);
+ GHO.approve(address(ROUTER), amount);
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel;
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({amount: amount, sender: alice, destChainSelector: ARB_CHAIN_SELECTOR})
+ );
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Burned(address(ARB_ON_RAMP), amount);
+ vm.expectEmit(address(ARB_ON_RAMP));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ ROUTER.ccipSend{value: eventArg.feeTokenAmount}(ARB_CHAIN_SELECTOR, message);
+
+ assertEq(GHO.balanceOf(alice), aliceBalance - amount);
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel - amount);
+ }
+
+ function test_sendMessageToEthSucceeds(uint256 amount) public {
+ amount = bound(amount, 1, CCIP_RATE_LIMIT_CAPACITY);
+ skip(_getOutboundRefillTime(amount)); // wait for the rate limiter to refill
+ // mock previously bridged amount
+ vm.prank(address(NEW_TOKEN_POOL));
+ GHO.mint(alice, amount); // increase bucket level
+
+ deal(address(GHO), alice, amount);
+ vm.prank(alice);
+ GHO.approve(address(ROUTER), amount);
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel;
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({amount: amount, sender: alice, destChainSelector: ETH_CHAIN_SELECTOR})
+ );
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Burned(address(ETH_ON_RAMP), amount);
+ vm.expectEmit(address(ETH_ON_RAMP));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ ROUTER.ccipSend{value: eventArg.feeTokenAmount}(ETH_CHAIN_SELECTOR, message);
+
+ assertEq(GHO.balanceOf(alice), aliceBalance - amount);
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel - amount);
+ }
+
+ function test_offRampViaArbSucceeds(uint256 amount) public {
+ amount = bound(
+ amount,
+ 1,
+ _min(
+ GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketCapacity, // initially, bucketLevel == 0
+ CCIP_RATE_LIMIT_CAPACITY
+ )
+ );
+ skip(_getInboundRefillTime(amount)); // wait for the rate limiter to refill
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Minted(address(ARB_OFF_RAMP), alice, amount);
+
+ vm.prank(address(ARB_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ARB_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ARB)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, amount);
+ assertEq(GHO.balanceOf(alice), aliceBalance + amount);
+ }
+
+ function test_offRampViaEthSucceeds(uint256 amount) public {
+ amount = bound(
+ amount,
+ 1,
+ _min(
+ GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketCapacity, // initially, bucketLevel == 0
+ CCIP_RATE_LIMIT_CAPACITY
+ )
+ );
+ skip(_getInboundRefillTime(amount)); // wait for the rate limiter to refill
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Minted(address(ETH_OFF_RAMP), alice, amount);
+
+ vm.prank(address(ETH_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ETH_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ETH)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, amount);
+ assertEq(GHO.balanceOf(alice), aliceBalance + amount);
+ }
+
+ function test_cannotOffRampOtherChainMessages() public {
+ uint256 amount = 100e18;
+ skip(_getInboundRefillTime(amount));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ InvalidSourcePoolAddress.selector,
+ abi.encode(address(NEW_REMOTE_POOL_ETH))
+ )
+ );
+ vm.prank(address(ARB_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ARB_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ETH)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ InvalidSourcePoolAddress.selector,
+ abi.encode(address(NEW_REMOTE_POOL_ARB))
+ )
+ );
+ vm.prank(address(ETH_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ETH_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ARB)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.sol
new file mode 100644
index 000000000..fc7259504
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.sol
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {IAaveV3ConfigEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol';
+import {AaveV3PayloadBase} from 'aave-helpers/src/v3-config-engine/AaveV3PayloadBase.sol';
+import {AaveV3Base} from 'aave-address-book/AaveV3Base.sol';
+
+/**
+ * @title GHO Base Listing
+ * @author Aave Labs
+ * - Discussion: https://governance.aave.com/t/arfc-launch-gho-on-base-set-aci-as-emissions-manager-for-rewards/19338
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x03dc21e0423c60082dc23317af6ebaf997610cbc2cbb0f5a385653bd99a524fe
+ */
+contract AaveV3Base_GHOBaseListing_20241223 is AaveV3PayloadBase {
+ using SafeERC20 for IERC20;
+
+ // https://basescan.org/address/0xac140648435d03f784879cd789130F22Ef588Fcd
+ address public constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd;
+ // https://basescan.org/address/0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73
+ address public constant GHO_PRICE_FEED = 0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73;
+ // https://basescan.org/address/0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee
+ address public constant GHO_TOKEN = 0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee;
+ uint256 public constant GHO_SEED_AMOUNT = 1e18;
+
+ function _preExecute() internal view override {
+ // robot should simulate and only execute if seed amount has been bridged, redundant check
+ assert(IERC20(GHO_TOKEN).balanceOf(address(this)) >= GHO_SEED_AMOUNT);
+ }
+
+ function _postExecute() internal override {
+ IERC20(GHO_TOKEN).forceApprove(address(AaveV3Base.POOL), GHO_SEED_AMOUNT);
+ AaveV3Base.POOL.supply(GHO_TOKEN, GHO_SEED_AMOUNT, address(0), 0);
+
+ (address aGhoToken, , ) = AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ GHO_TOKEN
+ );
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).setEmissionAdmin(GHO_TOKEN, EMISSION_ADMIN);
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).setEmissionAdmin(aGhoToken, EMISSION_ADMIN);
+ }
+
+ function newListings() public pure override returns (IAaveV3ConfigEngine.Listing[] memory) {
+ IAaveV3ConfigEngine.Listing[] memory listings = new IAaveV3ConfigEngine.Listing[](1);
+
+ listings[0] = IAaveV3ConfigEngine.Listing({
+ asset: GHO_TOKEN,
+ assetSymbol: 'GHO',
+ priceFeed: GHO_PRICE_FEED,
+ enabledToBorrow: EngineFlags.ENABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
+ ltv: 0,
+ liqThreshold: 0,
+ liqBonus: 0,
+ reserveFactor: 10_00,
+ supplyCap: 2_500_000,
+ borrowCap: 2_250_000,
+ debtCeiling: 0,
+ liqProtocolFee: 0,
+ rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({
+ optimalUsageRatio: 90_00,
+ baseVariableBorrowRate: 0,
+ variableRateSlope1: 12_00,
+ variableRateSlope2: 65_00
+ })
+ });
+
+ return listings;
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.t.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.t.sol
new file mode 100644
index 000000000..69ef16ed3
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.t.sol
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import 'forge-std/Test.sol';
+
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {DataTypes, IDefaultInterestRateStrategyV2} from 'aave-address-book/AaveV3.sol';
+import {IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol';
+import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+import {IGhoAaveSteward} from 'src/interfaces/IGhoAaveSteward.sol';
+import {IGhoBucketSteward} from 'src/interfaces/IGhoBucketSteward.sol';
+import {IGhoCcipSteward} from 'src/interfaces/IGhoCcipSteward.sol';
+import {IGhoToken} from 'src/interfaces/IGhoToken.sol';
+import {IGhoOracle} from 'src/interfaces/IGhoOracle.sol';
+
+import {ReserveConfiguration} from 'aave-v3-origin/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';
+import {Errors} from 'aave-address-book/governance-v3/Errors.sol';
+import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {GovV3Helpers} from 'aave-helpers/src/GovV3Helpers.sol';
+import {AaveV3Base} from 'aave-address-book/AaveV3Base.sol';
+import {GovernanceV3Base} from 'aave-address-book/GovernanceV3Base.sol';
+
+import {CCIPUtils} from './utils/CCIPUtils.sol';
+import {AaveV3Base_GHOBaseLaunch_20241223} from './AaveV3Base_GHOBaseLaunch_20241223.sol';
+import {AaveV3Base_GHOBaseListing_20241223} from './AaveV3Base_GHOBaseListing_20241223.sol';
+
+/**
+ * @dev Test for AaveV3Base_Ads_20241231
+ * command: FOUNDRY_PROFILE=base forge test --match-path=src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.t.sol -vv
+ */
+contract AaveV3Base_GHOBaseListing_20241223_Base is ProtocolV3TestBase {
+ AaveV3Base_GHOBaseListing_20241223 internal proposal;
+
+ ITokenAdminRegistry internal constant TOKEN_ADMIN_REGISTRY =
+ ITokenAdminRegistry(0x6f6C373d09C07425BaAE72317863d7F6bb731e37);
+ address internal constant ROUTER = 0x881e3A65B4d4a04dD529061dd0071cf975F58bCD;
+ address internal constant RMN_PROXY = 0xC842c69d54F83170C42C4d556B4F6B2ca53Dd3E8;
+ address internal constant RISK_COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60;
+ IGhoToken internal constant GHO_TOKEN = IGhoToken(0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee);
+ address internal constant NEW_REMOTE_POOL_ARB = 0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB;
+ address internal constant NEW_REMOTE_POOL_ETH = 0x06179f7C1be40863405f374E7f5F8806c728660A;
+ IGhoAaveSteward internal constant NEW_GHO_AAVE_STEWARD =
+ IGhoAaveSteward(0xC5BcC58BE6172769ca1a78B8A45752E3C5059c39);
+ IGhoBucketSteward internal constant NEW_GHO_BUCKET_STEWARD =
+ IGhoBucketSteward(0x3c47237479e7569653eF9beC4a7Cd2ee3F78b396);
+ IGhoCcipSteward internal constant NEW_GHO_CCIP_STEWARD =
+ IGhoCcipSteward(0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB);
+ IUpgradeableBurnMintTokenPool_1_5_1 internal constant NEW_TOKEN_POOL =
+ IUpgradeableBurnMintTokenPool_1_5_1(0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34);
+
+ function setUp() public virtual {
+ vm.createSelectFork(vm.rpcUrl('base'), 25415842);
+ proposal = new AaveV3Base_GHOBaseListing_20241223();
+ }
+
+ function _executeLaunchAIP() internal {
+ executePayload(vm, address(new AaveV3Base_GHOBaseLaunch_20241223()));
+ }
+
+ function _mockBridgeSeedAmount() internal {
+ uint256 seedAmount = proposal.GHO_SEED_AMOUNT();
+ vm.prank(address(NEW_TOKEN_POOL));
+ GHO_TOKEN.mint(GovernanceV3Base.EXECUTOR_LVL_1, seedAmount);
+ }
+
+ function _isDifferenceLowerThanMax(
+ uint256 from,
+ uint256 to,
+ uint256 max
+ ) internal pure returns (bool) {
+ return from < to ? to - from <= max : from - to <= max;
+ }
+
+ function assertEq(
+ IRateLimiter.TokenBucket memory bucket,
+ IRateLimiter.Config memory config
+ ) internal pure {
+ assertEq(bucket.capacity, config.capacity);
+ assertEq(bucket.rate, config.rate);
+ assertEq(bucket.isEnabled, config.isEnabled);
+ }
+
+ function assertEq(
+ IDefaultInterestRateStrategyV2.InterestRateData memory a,
+ IDefaultInterestRateStrategyV2.InterestRateData memory b
+ ) internal pure {
+ assertEq(a.optimalUsageRatio, b.optimalUsageRatio);
+ assertEq(a.baseVariableBorrowRate, b.baseVariableBorrowRate);
+ assertEq(a.variableRateSlope1, b.variableRateSlope1);
+ assertEq(a.variableRateSlope2, b.variableRateSlope2);
+ }
+}
+
+contract AaveV3Base_GHOBaseListing_20241223_ListingPreRequisites is
+ AaveV3Base_GHOBaseListing_20241223_Base
+{
+ uint40 internal payloadId;
+
+ function setUp() public override {
+ super.setUp();
+ payloadId = GovV3Helpers.readyPayload(vm, address(proposal));
+ }
+
+ function test_listingFailsPreLaunch() public {
+ vm.expectRevert(bytes(Errors.FAILED_ACTION_EXECUTION));
+ GovernanceV3Base.PAYLOADS_CONTROLLER.executePayload(payloadId);
+ }
+
+ function test_listingFailsWithoutSeedAmount() public {
+ test_listingFailsPreLaunch();
+ _executeLaunchAIP(); // activates CCIP lane
+
+ vm.expectRevert(bytes(Errors.FAILED_ACTION_EXECUTION)); // assertion failed on _preExecute()
+ GovernanceV3Base.PAYLOADS_CONTROLLER.executePayload(payloadId);
+ }
+
+ function test_listingSucceedsOnlyAfterLaunchAndSeedAmount() public {
+ test_listingFailsWithoutSeedAmount();
+ _mockBridgeSeedAmount();
+
+ GovernanceV3Base.PAYLOADS_CONTROLLER.executePayload(payloadId);
+
+ (, , , , , , , , bool isActive, ) = AaveV3Base
+ .AAVE_PROTOCOL_DATA_PROVIDER
+ .getReserveConfigurationData(proposal.GHO_TOKEN());
+ assertTrue(isActive);
+ }
+}
+
+contract AaveV3Base_GHOBaseListing_20241223_Listing is AaveV3Base_GHOBaseListing_20241223_Base {
+ function setUp() public override {
+ super.setUp();
+ _executeLaunchAIP(); // deploys gho token, token pool & stewards
+ _mockBridgeSeedAmount();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest('AaveV3Base_GHOBaseListing_20241223', AaveV3Base.POOL, address(proposal));
+
+ (address aGhoToken, , ) = AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.GHO_TOKEN()
+ );
+ assertGe(IERC20(aGhoToken).balanceOf(address(0)), proposal.GHO_SEED_AMOUNT());
+ }
+
+ function test_ghoPriceFeed() public view {
+ IGhoOracle priceOracle = IGhoOracle(proposal.GHO_PRICE_FEED());
+ assertEq(priceOracle.latestAnswer(), 1e8);
+ assertEq(priceOracle.decimals(), 8);
+ }
+
+ function test_ghoAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aGhoToken, , ) = AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.GHO_TOKEN()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).getEmissionAdmin(proposal.GHO_TOKEN()),
+ proposal.EMISSION_ADMIN()
+ );
+ assertEq(
+ IEmissionManager(AaveV3Base.EMISSION_MANAGER).getEmissionAdmin(aGhoToken),
+ proposal.EMISSION_ADMIN()
+ );
+ }
+}
+
+contract AaveV3Base_GHOBaseListing_20241223_Stewards is AaveV3Base_GHOBaseListing_20241223_Base {
+ using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
+
+ function setUp() public override {
+ super.setUp();
+ _executeLaunchAIP(); // deploys gho token, token pool & stewards
+ _mockBridgeSeedAmount();
+
+ executePayload(vm, address(proposal));
+ }
+
+ function test_aaveStewardCanUpdateBorrowRate() public {
+ IDefaultInterestRateStrategyV2 irStrategy = IDefaultInterestRateStrategyV2(
+ AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER.getInterestRateStrategyAddress(address(GHO_TOKEN))
+ );
+
+ IDefaultInterestRateStrategyV2.InterestRateData
+ memory currentRateData = IDefaultInterestRateStrategyV2.InterestRateData({
+ optimalUsageRatio: 90_00,
+ baseVariableBorrowRate: 0,
+ variableRateSlope1: 12_00,
+ variableRateSlope2: 65_00
+ });
+
+ assertEq(irStrategy.getInterestRateDataBps(address(GHO_TOKEN)), currentRateData);
+
+ currentRateData.variableRateSlope1 -= 4_00;
+ currentRateData.variableRateSlope2 -= 3_00;
+
+ vm.prank(RISK_COUNCIL);
+ NEW_GHO_AAVE_STEWARD.updateGhoBorrowRate(
+ currentRateData.optimalUsageRatio,
+ currentRateData.baseVariableBorrowRate,
+ currentRateData.variableRateSlope1,
+ currentRateData.variableRateSlope2
+ );
+
+ assertEq(irStrategy.getInterestRateDataBps(address(GHO_TOKEN)), currentRateData);
+ }
+
+ function test_aaveStewardCanUpdateBorrowCap(uint256 newBorrowCap) public {
+ uint256 currentBorrowCap = AaveV3Base.POOL.getConfiguration(address(GHO_TOKEN)).getBorrowCap();
+ assertEq(currentBorrowCap, 2_250_000);
+ vm.assume(
+ newBorrowCap != currentBorrowCap &&
+ _isDifferenceLowerThanMax(currentBorrowCap, newBorrowCap, currentBorrowCap)
+ );
+
+ vm.prank(RISK_COUNCIL);
+ NEW_GHO_AAVE_STEWARD.updateGhoBorrowCap(newBorrowCap);
+
+ assertEq(AaveV3Base.POOL.getConfiguration(address(GHO_TOKEN)).getBorrowCap(), newBorrowCap);
+ assertEq(NEW_GHO_AAVE_STEWARD.getGhoTimelocks().ghoBorrowCapLastUpdate, vm.getBlockTimestamp());
+ }
+
+ function test_aaveStewardCanUpdateSupplyCap(uint256 newSupplyCap) public {
+ uint256 currentSupplyCap = AaveV3Base.POOL.getConfiguration(address(GHO_TOKEN)).getSupplyCap();
+ assertEq(currentSupplyCap, 2_500_000);
+
+ vm.assume(
+ currentSupplyCap != newSupplyCap &&
+ _isDifferenceLowerThanMax(currentSupplyCap, newSupplyCap, currentSupplyCap)
+ );
+
+ vm.prank(RISK_COUNCIL);
+ NEW_GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap);
+
+ assertEq(AaveV3Base.POOL.getConfiguration(address(GHO_TOKEN)).getSupplyCap(), newSupplyCap);
+ assertEq(NEW_GHO_AAVE_STEWARD.getGhoTimelocks().ghoSupplyCapLastUpdate, vm.getBlockTimestamp());
+ }
+
+ function test_bucketStewardCanUpdateBucketCapacity(uint256 newBucketCapacity) public {
+ (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(NEW_TOKEN_POOL));
+ assertEq(currentBucketCapacity, 20_000_000e18);
+ newBucketCapacity = bound(
+ newBucketCapacity,
+ currentBucketCapacity + 1,
+ 2 * currentBucketCapacity
+ );
+
+ vm.startPrank(RISK_COUNCIL);
+ NEW_GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(
+ address(NEW_TOKEN_POOL),
+ uint128(newBucketCapacity)
+ );
+
+ assertEq(
+ GHO_TOKEN.getFacilitator(address(NEW_TOKEN_POOL)).bucketCapacity,
+ uint128(newBucketCapacity)
+ );
+ }
+
+ function test_ccipStewardCanChangeAndDisableRateLimit() public {
+ _runCcipStewardCanChangeAndDisableRateLimit(CCIPUtils.ETH_CHAIN_SELECTOR);
+ skip(NEW_GHO_CCIP_STEWARD.MINIMUM_DELAY() + 1);
+ _runCcipStewardCanChangeAndDisableRateLimit(CCIPUtils.ARB_CHAIN_SELECTOR);
+ }
+
+ function _runCcipStewardCanChangeAndDisableRateLimit(uint64 remoteChain) internal {
+ IRateLimiter.Config memory outboundConfig = IRateLimiter.Config({
+ isEnabled: true,
+ capacity: 500_000e18,
+ rate: 100e18
+ });
+ IRateLimiter.Config memory inboundConfig = IRateLimiter.Config({
+ isEnabled: true,
+ capacity: 100_000e18,
+ rate: 50e18
+ });
+
+ // we assert the steward can change the rate limit
+ vm.prank(NEW_GHO_CCIP_STEWARD.RISK_COUNCIL());
+ NEW_GHO_CCIP_STEWARD.updateRateLimit(
+ remoteChain,
+ outboundConfig.isEnabled,
+ outboundConfig.capacity,
+ outboundConfig.rate,
+ inboundConfig.isEnabled,
+ inboundConfig.capacity,
+ inboundConfig.rate
+ );
+
+ assertEq(NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(remoteChain), outboundConfig);
+ assertEq(NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(remoteChain), inboundConfig);
+ assertEq(NEW_GHO_CCIP_STEWARD.getCcipTimelocks().rateLimitLastUpdate, vm.getBlockTimestamp());
+
+ skip(NEW_GHO_CCIP_STEWARD.MINIMUM_DELAY() + 1);
+
+ // now we assert the steward can disable the rate limit
+ vm.prank(NEW_GHO_CCIP_STEWARD.RISK_COUNCIL());
+ NEW_GHO_CCIP_STEWARD.updateRateLimit(remoteChain, false, 0, 0, false, 0, 0);
+
+ IRateLimiter.Config memory disabledConfig = IRateLimiter.Config(false, 0, 0);
+ assertEq(NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(remoteChain), disabledConfig);
+ assertEq(NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(remoteChain), disabledConfig);
+ assertEq(NEW_GHO_CCIP_STEWARD.getCcipTimelocks().rateLimitLastUpdate, vm.getBlockTimestamp());
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3E2E_GHOBaseLaunch_20241223.t.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3E2E_GHOBaseLaunch_20241223.t.sol
new file mode 100644
index 000000000..f4abbf486
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3E2E_GHOBaseLaunch_20241223.t.sol
@@ -0,0 +1,808 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import 'forge-std/Test.sol';
+
+import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol';
+
+import {IUpgradeableLockReleaseTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol';
+import {IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+import {IInternal} from 'src/interfaces/ccip/IInternal.sol';
+import {IClient} from 'src/interfaces/ccip/IClient.sol';
+import {IRouter} from 'src/interfaces/ccip/IRouter.sol';
+import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol';
+import {IEVM2EVMOffRamp_1_5} from 'src/interfaces/ccip/IEVM2EVMOffRamp.sol';
+import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol';
+import {IPriceRegistry} from 'src/interfaces/ccip/IPriceRegistry.sol';
+import {IGhoToken} from 'src/interfaces/IGhoToken.sol';
+import {IGhoAaveSteward} from 'src/interfaces/IGhoAaveSteward.sol';
+import {IGhoBucketSteward} from 'src/interfaces/IGhoBucketSteward.sol';
+import {IGhoCcipSteward} from 'src/interfaces/IGhoCcipSteward.sol';
+
+import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+
+import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol';
+
+import {CCIPUtils} from './utils/CCIPUtils.sol';
+import {AaveV3Arbitrum_GHOBaseLaunch_20241223} from './AaveV3Arbitrum_GHOBaseLaunch_20241223.sol';
+import {AaveV3Base_GHOBaseLaunch_20241223} from './AaveV3Base_GHOBaseLaunch_20241223.sol';
+import {AaveV3Ethereum_GHOBaseLaunch_20241223} from './AaveV3Ethereum_GHOBaseLaunch_20241223.sol';
+
+import {AaveV3Arbitrum_GHOCCIP151Upgrade_20241209} from '../20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol';
+import {AaveV3Ethereum_GHOCCIP151Upgrade_20241209} from '../20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol';
+
+/**
+ * @dev Test for AaveV3Base_GHOBaseLaunch_20241223
+ * command: FOUNDRY_PROFILE=base forge test --match-path=src/20241223_Multi_GHOBaseLaunch/AaveV3E2E_GHOBaseLaunch_20241223.t.sol -vv
+ */
+contract AaveV3Base_GHOBaseLaunch_20241223_Base is ProtocolV3TestBase {
+ struct Common {
+ IRouter router;
+ IGhoToken token;
+ IEVM2EVMOnRamp arbOnRamp;
+ IEVM2EVMOnRamp baseOnRamp;
+ IEVM2EVMOnRamp ethOnRamp;
+ IEVM2EVMOffRamp_1_5 arbOffRamp;
+ IEVM2EVMOffRamp_1_5 baseOffRamp;
+ IEVM2EVMOffRamp_1_5 ethOffRamp;
+ ITokenAdminRegistry tokenAdminRegistry;
+ uint64 chainSelector;
+ uint256 forkId;
+ }
+
+ struct CCIPSendParams {
+ Common src;
+ Common dst;
+ uint256 amount;
+ address sender;
+ }
+
+ struct ARB {
+ AaveV3Arbitrum_GHOBaseLaunch_20241223 proposal;
+ IUpgradeableBurnMintTokenPool_1_5_1 tokenPool;
+ Common c;
+ }
+ struct BASE {
+ AaveV3Base_GHOBaseLaunch_20241223 proposal;
+ IUpgradeableBurnMintTokenPool_1_5_1 tokenPool;
+ Common c;
+ }
+ struct ETH {
+ AaveV3Ethereum_GHOBaseLaunch_20241223 proposal;
+ IUpgradeableLockReleaseTokenPool_1_5_1 tokenPool;
+ Common c;
+ }
+
+ address internal constant RISK_COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60; // common across all chains
+ address internal constant RMN_PROXY_BASE = 0xC842c69d54F83170C42C4d556B4F6B2ca53Dd3E8;
+ address internal constant ROUTER_BASE = 0x881e3A65B4d4a04dD529061dd0071cf975F58bCD;
+ address internal constant GHO_TOKEN_IMPL_BASE = 0xb0e1c7830aA781362f79225559Aa068E6bDaF1d1;
+ IGhoToken internal constant GHO_TOKEN_BASE =
+ IGhoToken(0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee);
+ uint256 internal constant CCIP_RATE_LIMIT_CAPACITY = 300_000e18;
+ uint256 internal constant CCIP_RATE_LIMIT_REFILL_RATE = 60e18;
+
+ ARB internal arb;
+ BASE internal base;
+ ETH internal eth;
+
+ address internal alice = makeAddr('alice');
+ address internal bob = makeAddr('bob');
+ address internal carol = makeAddr('carol');
+
+ IGhoAaveSteward internal GHO_AAVE_STEWARD_BASE;
+ IGhoBucketSteward internal GHO_BUCKET_STEWARD_BASE;
+ IGhoCcipSteward internal GHO_CCIP_STEWARD_BASE;
+
+ event CCIPSendRequested(IInternal.EVM2EVMMessage message);
+ event Locked(address indexed sender, uint256 amount);
+ event Burned(address indexed sender, uint256 amount);
+ event Released(address indexed sender, address indexed recipient, uint256 amount);
+ event Minted(address indexed sender, address indexed recipient, uint256 amount);
+
+ function setUp() public virtual {
+ arb.c.forkId = vm.createFork(vm.rpcUrl('arbitrum'), 298375852);
+ base.c.forkId = vm.createFork(vm.rpcUrl('base'), 25415842);
+ eth.c.forkId = vm.createFork(vm.rpcUrl('mainnet'), 21686002);
+
+ arb.c.chainSelector = 4949039107694359620;
+ base.c.chainSelector = 15971525489660198786;
+ eth.c.chainSelector = 5009297550715157269;
+
+ vm.selectFork(arb.c.forkId);
+ // pre-requisite, to be removed after execution
+ executePayload(vm, address(new AaveV3Arbitrum_GHOCCIP151Upgrade_20241209()));
+ arb.proposal = new AaveV3Arbitrum_GHOBaseLaunch_20241223();
+ arb.c.token = IGhoToken(AaveV3ArbitrumAssets.GHO_UNDERLYING);
+ arb.tokenPool = IUpgradeableBurnMintTokenPool_1_5_1(0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB);
+ arb.c.tokenAdminRegistry = ITokenAdminRegistry(0x39AE1032cF4B334a1Ed41cdD0833bdD7c7E7751E);
+ arb.c.router = IRouter(arb.tokenPool.getRouter());
+ arb.c.baseOnRamp = IEVM2EVMOnRamp(arb.c.router.getOnRamp(base.c.chainSelector));
+ arb.c.ethOnRamp = IEVM2EVMOnRamp(arb.c.router.getOnRamp(eth.c.chainSelector));
+ arb.c.baseOffRamp = IEVM2EVMOffRamp_1_5(0xb62178f8198905D0Fa6d640Bdb188E4E8143Ac4b);
+ arb.c.ethOffRamp = IEVM2EVMOffRamp_1_5(0x91e46cc5590A4B9182e47f40006140A7077Dec31);
+
+ vm.selectFork(base.c.forkId);
+ base.proposal = new AaveV3Base_GHOBaseLaunch_20241223();
+ base.tokenPool = IUpgradeableBurnMintTokenPool_1_5_1(
+ 0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34
+ );
+ base.c.tokenAdminRegistry = ITokenAdminRegistry(0x6f6C373d09C07425BaAE72317863d7F6bb731e37);
+ base.c.token = GHO_TOKEN_BASE;
+ base.c.router = IRouter(base.tokenPool.getRouter());
+ base.c.arbOnRamp = IEVM2EVMOnRamp(base.c.router.getOnRamp(arb.c.chainSelector));
+ base.c.ethOnRamp = IEVM2EVMOnRamp(base.c.router.getOnRamp(eth.c.chainSelector));
+ base.c.arbOffRamp = IEVM2EVMOffRamp_1_5(0x7D38c6363d5E4DFD500a691Bc34878b383F58d93);
+ base.c.ethOffRamp = IEVM2EVMOffRamp_1_5(0xCA04169671A81E4fB8768cfaD46c347ae65371F1);
+
+ vm.selectFork(eth.c.forkId);
+ // pre-requisite, to be removed after execution
+ executePayload(vm, address(new AaveV3Ethereum_GHOCCIP151Upgrade_20241209()));
+ eth.proposal = new AaveV3Ethereum_GHOBaseLaunch_20241223();
+ eth.c.token = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING);
+ eth.tokenPool = IUpgradeableLockReleaseTokenPool_1_5_1(
+ 0x06179f7C1be40863405f374E7f5F8806c728660A
+ );
+ eth.c.tokenAdminRegistry = ITokenAdminRegistry(0xb22764f98dD05c789929716D677382Df22C05Cb6);
+ eth.c.router = IRouter(eth.tokenPool.getRouter());
+ eth.c.arbOnRamp = IEVM2EVMOnRamp(eth.c.router.getOnRamp(arb.c.chainSelector));
+ eth.c.baseOnRamp = IEVM2EVMOnRamp(eth.c.router.getOnRamp(base.c.chainSelector));
+ eth.c.arbOffRamp = IEVM2EVMOffRamp_1_5(0xdf615eF8D4C64d0ED8Fd7824BBEd2f6a10245aC9);
+ eth.c.baseOffRamp = IEVM2EVMOffRamp_1_5(0x6B4B6359Dd5B47Cdb030E5921456D2a0625a9EbD);
+
+ _validateConfig({executed: false});
+ }
+
+ function _getTokenMessage(
+ CCIPSendParams memory params
+ ) internal returns (IClient.EVM2AnyMessage memory, IInternal.EVM2EVMMessage memory) {
+ IClient.EVM2AnyMessage memory message = CCIPUtils.generateMessage(params.sender, 1);
+ message.tokenAmounts[0] = IClient.EVMTokenAmount({
+ token: address(params.src.token),
+ amount: params.amount
+ });
+
+ uint256 feeAmount = params.src.router.getFee(params.dst.chainSelector, message);
+ deal(params.sender, feeAmount);
+
+ IInternal.EVM2EVMMessage memory eventArg = CCIPUtils.messageToEvent(
+ CCIPUtils.MessageToEventParams({
+ message: message,
+ router: params.src.router,
+ sourceChainSelector: params.src.chainSelector,
+ destChainSelector: params.dst.chainSelector,
+ feeTokenAmount: feeAmount,
+ originalSender: params.sender,
+ sourceToken: address(params.src.token),
+ destinationToken: address(params.dst.token)
+ })
+ );
+
+ return (message, eventArg);
+ }
+
+ function _validateConfig(bool executed) internal {
+ vm.selectFork(arb.c.forkId);
+ assertEq(arb.c.chainSelector, 4949039107694359620);
+ assertEq(address(arb.c.token), AaveV3ArbitrumAssets.GHO_UNDERLYING);
+ assertEq(arb.c.router.typeAndVersion(), 'Router 1.2.0');
+ _assertOnRamp(arb.c.baseOnRamp, arb.c.chainSelector, base.c.chainSelector, arb.c.router);
+ _assertOnRamp(arb.c.ethOnRamp, arb.c.chainSelector, eth.c.chainSelector, arb.c.router);
+ _assertOffRamp(arb.c.baseOffRamp, base.c.chainSelector, arb.c.chainSelector, arb.c.router);
+ _assertOffRamp(arb.c.ethOffRamp, eth.c.chainSelector, arb.c.chainSelector, arb.c.router);
+
+ // proposal constants
+ assertEq(arb.proposal.BASE_CHAIN_SELECTOR(), base.c.chainSelector);
+ assertEq(address(arb.proposal.TOKEN_POOL()), address(arb.tokenPool));
+ assertEq(arb.proposal.REMOTE_TOKEN_POOL_BASE(), address(base.tokenPool));
+ assertEq(arb.proposal.REMOTE_GHO_TOKEN_BASE(), address(base.c.token));
+
+ vm.selectFork(base.c.forkId);
+ assertEq(base.c.chainSelector, 15971525489660198786);
+ assertEq(base.c.router.typeAndVersion(), 'Router 1.2.0');
+ _assertOnRamp(base.c.arbOnRamp, base.c.chainSelector, arb.c.chainSelector, base.c.router);
+ _assertOnRamp(base.c.ethOnRamp, base.c.chainSelector, eth.c.chainSelector, base.c.router);
+ _assertOffRamp(base.c.arbOffRamp, arb.c.chainSelector, base.c.chainSelector, base.c.router);
+ _assertOffRamp(base.c.ethOffRamp, eth.c.chainSelector, base.c.chainSelector, base.c.router);
+
+ // proposal constants
+ assertEq(base.proposal.ETH_CHAIN_SELECTOR(), eth.c.chainSelector);
+ assertEq(base.proposal.ARB_CHAIN_SELECTOR(), arb.c.chainSelector);
+ assertEq(base.proposal.CCIP_BUCKET_CAPACITY(), 20_000_000e18);
+ assertEq(address(base.proposal.TOKEN_ADMIN_REGISTRY()), address(base.c.tokenAdminRegistry));
+ assertEq(address(base.proposal.TOKEN_POOL()), address(base.tokenPool));
+ IGhoCcipSteward ghoCcipSteward = IGhoCcipSteward(base.proposal.GHO_CCIP_STEWARD());
+ assertEq(ghoCcipSteward.GHO_TOKEN_POOL(), address(base.tokenPool));
+ assertEq(ghoCcipSteward.GHO_TOKEN(), address(base.c.token));
+ assertEq(base.proposal.REMOTE_TOKEN_POOL_ETH(), address(eth.tokenPool));
+ assertEq(base.proposal.REMOTE_TOKEN_POOL_ARB(), address(arb.tokenPool));
+
+ vm.selectFork(eth.c.forkId);
+ assertEq(eth.c.chainSelector, 5009297550715157269);
+ assertEq(address(eth.c.token), AaveV3EthereumAssets.GHO_UNDERLYING);
+ assertEq(eth.c.router.typeAndVersion(), 'Router 1.2.0');
+ _assertOnRamp(eth.c.arbOnRamp, eth.c.chainSelector, arb.c.chainSelector, eth.c.router);
+ _assertOnRamp(eth.c.baseOnRamp, eth.c.chainSelector, base.c.chainSelector, eth.c.router);
+ _assertOffRamp(eth.c.arbOffRamp, arb.c.chainSelector, eth.c.chainSelector, eth.c.router);
+ _assertOffRamp(eth.c.baseOffRamp, base.c.chainSelector, eth.c.chainSelector, eth.c.router);
+
+ // proposal constants
+ assertEq(eth.proposal.BASE_CHAIN_SELECTOR(), base.c.chainSelector);
+ assertEq(address(eth.proposal.TOKEN_POOL()), address(eth.tokenPool));
+ assertEq(eth.proposal.REMOTE_TOKEN_POOL_BASE(), address(base.tokenPool));
+ assertEq(eth.proposal.REMOTE_GHO_TOKEN_BASE(), address(base.c.token));
+
+ if (executed) {
+ vm.selectFork(arb.c.forkId);
+ assertEq(arb.c.tokenAdminRegistry.getPool(address(arb.c.token)), address(arb.tokenPool));
+ assertEq(arb.tokenPool.getSupportedChains()[0], eth.c.chainSelector);
+ assertEq(arb.tokenPool.getSupportedChains()[1], base.c.chainSelector);
+ assertEq(arb.tokenPool.getRemoteToken(eth.c.chainSelector), abi.encode(address(eth.c.token)));
+ assertEq(
+ arb.tokenPool.getRemoteToken(base.c.chainSelector),
+ abi.encode(address(base.c.token))
+ );
+ assertEq(arb.tokenPool.getRemotePools(base.c.chainSelector).length, 1);
+ assertEq(
+ arb.tokenPool.getRemotePools(base.c.chainSelector)[0],
+ abi.encode(address(base.tokenPool))
+ );
+ assertEq(arb.tokenPool.getRemotePools(eth.c.chainSelector).length, 2);
+ assertEq(
+ arb.tokenPool.getRemotePools(eth.c.chainSelector)[1], // 0th is the 1.4 token pool
+ abi.encode(address(eth.tokenPool))
+ );
+ _assertSetRateLimit(arb.c, address(arb.tokenPool));
+
+ vm.selectFork(base.c.forkId);
+ assertEq(address(base.proposal.GHO_TOKEN()), address(base.c.token));
+ assertEq(base.c.tokenAdminRegistry.getPool(address(base.c.token)), address(base.tokenPool));
+ assertEq(base.tokenPool.getSupportedChains()[0], eth.c.chainSelector);
+ assertEq(base.tokenPool.getSupportedChains()[1], arb.c.chainSelector);
+ assertEq(
+ base.tokenPool.getRemoteToken(arb.c.chainSelector),
+ abi.encode(address(arb.c.token))
+ );
+ assertEq(
+ base.tokenPool.getRemoteToken(eth.c.chainSelector),
+ abi.encode(address(eth.c.token))
+ );
+ assertEq(base.tokenPool.getRemotePools(arb.c.chainSelector).length, 1);
+ assertEq(
+ base.tokenPool.getRemotePools(arb.c.chainSelector)[0],
+ abi.encode(address(arb.tokenPool))
+ );
+ assertEq(base.tokenPool.getRemotePools(eth.c.chainSelector).length, 1);
+ assertEq(
+ base.tokenPool.getRemotePools(eth.c.chainSelector)[0],
+ abi.encode(address(eth.tokenPool))
+ );
+ _assertSetRateLimit(base.c, address(base.tokenPool));
+
+ vm.selectFork(eth.c.forkId);
+ assertEq(eth.c.tokenAdminRegistry.getPool(address(eth.c.token)), address(eth.tokenPool));
+ assertEq(eth.tokenPool.getSupportedChains()[0], arb.c.chainSelector);
+ assertEq(eth.tokenPool.getSupportedChains()[1], base.c.chainSelector);
+ assertEq(eth.tokenPool.getRemoteToken(arb.c.chainSelector), abi.encode(address(arb.c.token)));
+ assertEq(
+ eth.tokenPool.getRemoteToken(base.c.chainSelector),
+ abi.encode(address(base.c.token))
+ );
+ assertEq(eth.tokenPool.getRemotePools(arb.c.chainSelector).length, 2);
+ assertEq(
+ eth.tokenPool.getRemotePools(arb.c.chainSelector)[1], // 0th is the 1.4 token pool
+ abi.encode(address(arb.tokenPool))
+ );
+ assertEq(eth.tokenPool.getRemotePools(base.c.chainSelector).length, 1);
+ assertEq(
+ eth.tokenPool.getRemotePools(base.c.chainSelector)[0],
+ abi.encode(address(base.tokenPool))
+ );
+ _assertSetRateLimit(eth.c, address(eth.tokenPool));
+ }
+ }
+
+ function _assertOnRamp(
+ IEVM2EVMOnRamp onRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(onRamp.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0');
+ assertEq(onRamp.getStaticConfig().chainSelector, srcSelector);
+ assertEq(onRamp.getStaticConfig().destChainSelector, dstSelector);
+ assertEq(onRamp.getDynamicConfig().router, address(router));
+ assertEq(router.getOnRamp(dstSelector), address(onRamp));
+ }
+
+ function _assertOffRamp(
+ IEVM2EVMOffRamp_1_5 offRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(offRamp.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0');
+ assertEq(offRamp.getStaticConfig().sourceChainSelector, srcSelector);
+ assertEq(offRamp.getStaticConfig().chainSelector, dstSelector);
+ assertEq(offRamp.getDynamicConfig().router, address(router));
+ assertTrue(router.isOffRamp(srcSelector, address(offRamp)));
+ }
+
+ function _assertSetRateLimit(Common memory src, address tokenPool) internal view {
+ (Common memory dst1, Common memory dst2) = _getDestination(src);
+ IUpgradeableLockReleaseTokenPool_1_5_1 _tokenPool = IUpgradeableLockReleaseTokenPool_1_5_1(
+ tokenPool
+ );
+ assertEq(
+ _tokenPool.getCurrentInboundRateLimiterState(dst1.chainSelector),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ _tokenPool.getCurrentOutboundRateLimiterState(dst1.chainSelector),
+ _getRateLimiterConfig()
+ );
+
+ assertEq(
+ _tokenPool.getCurrentInboundRateLimiterState(dst2.chainSelector),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ _tokenPool.getCurrentOutboundRateLimiterState(dst2.chainSelector),
+ _getRateLimiterConfig()
+ );
+ }
+
+ function _getDestination(Common memory src) internal view returns (Common memory, Common memory) {
+ if (src.forkId == arb.c.forkId) return (base.c, eth.c);
+ else if (src.forkId == base.c.forkId) return (arb.c, eth.c);
+ else return (arb.c, base.c);
+ }
+
+ function _tokenBucketToConfig(
+ IRateLimiter.TokenBucket memory bucket
+ ) internal pure returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: bucket.isEnabled,
+ capacity: bucket.capacity,
+ rate: bucket.rate
+ });
+ }
+
+ function _getDisabledConfig() internal pure returns (IRateLimiter.Config memory) {
+ return IRateLimiter.Config({isEnabled: false, capacity: 0, rate: 0});
+ }
+
+ function _getImplementation(address proxy) internal view returns (address) {
+ bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
+ return address(uint160(uint256(vm.load(proxy, slot))));
+ }
+
+ function _readInitialized(address proxy) internal view returns (uint8) {
+ return uint8(uint256(vm.load(proxy, bytes32(0))));
+ }
+
+ function _getRateLimiterConfig() internal pure returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: true,
+ capacity: uint128(CCIP_RATE_LIMIT_CAPACITY),
+ rate: uint128(CCIP_RATE_LIMIT_REFILL_RATE)
+ });
+ }
+
+ function _getOutboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return (amount / CCIP_RATE_LIMIT_REFILL_RATE) + 1; // account for rounding
+ }
+
+ function _getInboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return (amount / CCIP_RATE_LIMIT_REFILL_RATE) + 1; // account for rounding
+ }
+
+ function _min(uint256 a, uint256 b) internal pure returns (uint256) {
+ return a < b ? a : b;
+ }
+
+ function assertEq(
+ IRateLimiter.TokenBucket memory bucket,
+ IRateLimiter.Config memory config
+ ) internal pure {
+ assertEq(abi.encode(_tokenBucketToConfig(bucket)), abi.encode(config));
+ }
+
+ // @dev refresh token prices to the last stored such that price is not stale
+ // @dev assumed src.forkId is already active
+ function _refreshGasAndTokenPrices(Common memory src, Common memory dst) internal {
+ uint64 destChainSelector = dst.chainSelector;
+ IEVM2EVMOnRamp srcOnRamp = IEVM2EVMOnRamp(src.router.getOnRamp(destChainSelector));
+ address bridgeToken = address(src.token);
+ address feeToken = src.router.getWrappedNative(); // needed as we do tests with wrapped native as fee token
+ address linkToken = srcOnRamp.getStaticConfig().linkToken; // needed as feeTokenAmount is converted to linkTokenAmount
+ IInternal.TokenPriceUpdate[] memory tokenPriceUpdates = new IInternal.TokenPriceUpdate[](3);
+ IInternal.GasPriceUpdate[] memory gasPriceUpdates = new IInternal.GasPriceUpdate[](1);
+ IPriceRegistry priceRegistry = IPriceRegistry(srcOnRamp.getDynamicConfig().priceRegistry); // both ramps have the same price registry
+
+ tokenPriceUpdates[0] = IInternal.TokenPriceUpdate({
+ sourceToken: bridgeToken,
+ usdPerToken: priceRegistry.getTokenPrice(bridgeToken).value
+ });
+ tokenPriceUpdates[1] = IInternal.TokenPriceUpdate({
+ sourceToken: feeToken,
+ usdPerToken: priceRegistry.getTokenPrice(feeToken).value
+ });
+ tokenPriceUpdates[2] = IInternal.TokenPriceUpdate({
+ sourceToken: linkToken,
+ usdPerToken: priceRegistry.getTokenPrice(linkToken).value
+ });
+
+ gasPriceUpdates[0] = IInternal.GasPriceUpdate({
+ destChainSelector: destChainSelector,
+ usdPerUnitGas: priceRegistry.getDestinationChainGasPrice(destChainSelector).value
+ });
+
+ vm.prank(priceRegistry.owner());
+ priceRegistry.updatePrices(
+ IInternal.PriceUpdates({
+ tokenPriceUpdates: tokenPriceUpdates,
+ gasPriceUpdates: gasPriceUpdates
+ })
+ );
+ }
+}
+
+contract AaveV3Base_GHOBaseLaunch_20241223_PostExecution is AaveV3Base_GHOBaseLaunch_20241223_Base {
+ function setUp() public override {
+ super.setUp();
+
+ vm.selectFork(arb.c.forkId);
+ executePayload(vm, address(arb.proposal));
+
+ vm.selectFork(eth.c.forkId);
+ executePayload(vm, address(eth.proposal));
+
+ vm.selectFork(base.c.forkId);
+ executePayload(vm, address(base.proposal));
+
+ _validateConfig({executed: true});
+ }
+
+ function test_E2E_Eth_Base(uint256 amount) public {
+ {
+ vm.selectFork(eth.c.forkId);
+ uint256 bridgeableAmount = _min(
+ eth.tokenPool.getBridgeLimit() - eth.tokenPool.getCurrentBridgedAmount(),
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getOutboundRefillTime(amount));
+ _refreshGasAndTokenPrices(eth.c, base.c);
+
+ vm.prank(alice);
+ eth.c.token.approve(address(eth.c.router), amount);
+ deal(address(eth.c.token), alice, amount);
+
+ uint256 tokenPoolBalance = eth.c.token.balanceOf(address(eth.tokenPool));
+ uint256 aliceBalance = eth.c.token.balanceOf(alice);
+ uint256 bridgedAmount = eth.tokenPool.getCurrentBridgedAmount();
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({src: eth.c, dst: base.c, sender: alice, amount: amount})
+ );
+
+ vm.expectEmit(address(eth.tokenPool));
+ emit Locked(address(eth.c.baseOnRamp), amount);
+ vm.expectEmit(address(eth.c.baseOnRamp));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ eth.c.router.ccipSend{value: eventArg.feeTokenAmount}(base.c.chainSelector, message);
+
+ assertEq(eth.c.token.balanceOf(address(eth.tokenPool)), tokenPoolBalance + amount);
+ assertEq(eth.c.token.balanceOf(alice), aliceBalance - amount);
+ assertEq(eth.tokenPool.getCurrentBridgedAmount(), bridgedAmount + amount);
+
+ // base execute message
+ vm.selectFork(base.c.forkId);
+
+ skip(_getInboundRefillTime(amount));
+ _refreshGasAndTokenPrices(base.c, eth.c);
+ assertEq(base.c.token.balanceOf(alice), 0);
+ assertEq(base.c.token.totalSupply(), 0); // first bridge
+ assertEq(base.c.token.getFacilitator(address(base.tokenPool)).bucketLevel, 0); // first bridge
+
+ vm.expectEmit(address(base.tokenPool));
+ emit Minted(address(base.c.ethOffRamp), alice, amount);
+
+ vm.prank(address(base.c.ethOffRamp));
+ base.c.ethOffRamp.executeSingleMessage({
+ message: eventArg,
+ offchainTokenData: new bytes[](message.tokenAmounts.length),
+ tokenGasOverrides: new uint32[](0)
+ });
+
+ assertEq(base.c.token.balanceOf(alice), amount);
+ assertEq(base.c.token.getFacilitator(address(base.tokenPool)).bucketLevel, amount);
+ }
+
+ // send amount back to eth
+ {
+ // send base from base
+ vm.selectFork(base.c.forkId);
+
+ skip(_getOutboundRefillTime(amount));
+ _refreshGasAndTokenPrices(base.c, eth.c);
+ vm.prank(alice);
+ base.c.token.approve(address(base.c.router), amount);
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({src: base.c, dst: eth.c, sender: alice, amount: amount})
+ );
+
+ vm.expectEmit(address(base.tokenPool));
+ emit Burned(address(base.c.ethOnRamp), amount);
+ vm.expectEmit(address(base.c.ethOnRamp));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ base.c.router.ccipSend{value: eventArg.feeTokenAmount}(eth.c.chainSelector, message);
+
+ assertEq(base.c.token.balanceOf(alice), 0);
+ assertEq(base.c.token.getFacilitator(address(base.tokenPool)).bucketLevel, 0);
+
+ // eth execute message
+ vm.selectFork(eth.c.forkId);
+
+ skip(_getInboundRefillTime(amount));
+ _refreshGasAndTokenPrices(eth.c, base.c);
+ uint256 bridgedAmount = eth.tokenPool.getCurrentBridgedAmount();
+
+ vm.expectEmit(address(eth.tokenPool));
+ emit Released(address(eth.c.baseOffRamp), alice, amount);
+ vm.prank(address(eth.c.baseOffRamp));
+ eth.c.baseOffRamp.executeSingleMessage({
+ message: eventArg,
+ offchainTokenData: new bytes[](message.tokenAmounts.length),
+ tokenGasOverrides: new uint32[](0)
+ });
+
+ assertEq(eth.c.token.balanceOf(alice), amount);
+ assertEq(eth.tokenPool.getCurrentBridgedAmount(), bridgedAmount - amount);
+ }
+ }
+
+ function test_E2E_Arb_Base(uint256 amount) public {
+ {
+ vm.selectFork(arb.c.forkId);
+ uint256 bridgeableAmount = _min(
+ arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel,
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getOutboundRefillTime(amount));
+ _refreshGasAndTokenPrices(arb.c, base.c);
+
+ vm.prank(alice);
+ arb.c.token.approve(address(arb.c.router), amount);
+ deal(address(arb.c.token), alice, amount);
+
+ uint256 aliceBalance = arb.c.token.balanceOf(alice);
+ uint256 facilitatorLevel = arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel;
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({src: arb.c, dst: base.c, sender: alice, amount: amount})
+ );
+
+ vm.expectEmit(address(arb.tokenPool));
+ emit Burned(address(arb.c.baseOnRamp), amount);
+ vm.expectEmit(address(arb.c.baseOnRamp));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ arb.c.router.ccipSend{value: eventArg.feeTokenAmount}(base.c.chainSelector, message);
+
+ assertEq(arb.c.token.balanceOf(alice), aliceBalance - amount);
+ assertEq(
+ arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel,
+ facilitatorLevel - amount
+ );
+
+ // base execute message
+ vm.selectFork(base.c.forkId);
+
+ skip(_getInboundRefillTime(amount));
+ _refreshGasAndTokenPrices(base.c, arb.c);
+ assertEq(base.c.token.balanceOf(alice), 0);
+ assertEq(base.c.token.totalSupply(), 0); // first bridge
+ assertEq(base.c.token.getFacilitator(address(base.tokenPool)).bucketLevel, 0); // first bridge
+
+ vm.expectEmit(address(base.tokenPool));
+ emit Minted(address(base.c.arbOffRamp), alice, amount);
+
+ vm.prank(address(base.c.arbOffRamp));
+ base.c.arbOffRamp.executeSingleMessage({
+ message: eventArg,
+ offchainTokenData: new bytes[](message.tokenAmounts.length),
+ tokenGasOverrides: new uint32[](0)
+ });
+
+ assertEq(base.c.token.balanceOf(alice), amount);
+ assertEq(base.c.token.getFacilitator(address(base.tokenPool)).bucketLevel, amount);
+ }
+
+ // send amount back to arb
+ {
+ // send base from base
+ vm.selectFork(base.c.forkId);
+
+ skip(_getOutboundRefillTime(amount));
+ _refreshGasAndTokenPrices(base.c, arb.c);
+ vm.prank(alice);
+ base.c.token.approve(address(base.c.router), amount);
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({src: base.c, dst: arb.c, sender: alice, amount: amount})
+ );
+
+ vm.expectEmit(address(base.tokenPool));
+ emit Burned(address(base.c.arbOnRamp), amount);
+ vm.expectEmit(address(base.c.arbOnRamp));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ base.c.router.ccipSend{value: eventArg.feeTokenAmount}(arb.c.chainSelector, message);
+
+ assertEq(base.c.token.balanceOf(alice), 0);
+ assertEq(base.c.token.getFacilitator(address(base.tokenPool)).bucketLevel, 0);
+
+ // arb execute message
+ vm.selectFork(arb.c.forkId);
+
+ skip(_getInboundRefillTime(amount));
+ _refreshGasAndTokenPrices(arb.c, base.c);
+ uint256 facilitatorLevel = arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel;
+
+ vm.expectEmit(address(arb.tokenPool));
+ emit Minted(address(arb.c.baseOffRamp), alice, amount);
+ vm.prank(address(arb.c.baseOffRamp));
+ arb.c.baseOffRamp.executeSingleMessage({
+ message: eventArg,
+ offchainTokenData: new bytes[](message.tokenAmounts.length),
+ tokenGasOverrides: new uint32[](0)
+ });
+
+ assertEq(arb.c.token.balanceOf(alice), amount);
+ assertEq(
+ arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel,
+ facilitatorLevel + amount
+ );
+ }
+ }
+
+ function test_E2E_Eth_Arb(uint256 amount) public {
+ {
+ vm.selectFork(eth.c.forkId);
+ uint256 bridgeableAmount = _min(
+ eth.tokenPool.getBridgeLimit() - eth.tokenPool.getCurrentBridgedAmount(),
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getOutboundRefillTime(amount));
+ _refreshGasAndTokenPrices(eth.c, arb.c);
+
+ vm.prank(alice);
+ eth.c.token.approve(address(eth.c.router), amount);
+ deal(address(eth.c.token), alice, amount);
+
+ uint256 tokenPoolBalance = eth.c.token.balanceOf(address(eth.tokenPool));
+ uint256 aliceBalance = eth.c.token.balanceOf(alice);
+ uint256 bridgedAmount = eth.tokenPool.getCurrentBridgedAmount();
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(CCIPSendParams({src: eth.c, dst: arb.c, sender: alice, amount: amount}));
+
+ vm.expectEmit(address(eth.tokenPool));
+ emit Locked(address(eth.c.arbOnRamp), amount);
+ vm.expectEmit(address(eth.c.arbOnRamp));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ eth.c.router.ccipSend{value: eventArg.feeTokenAmount}(arb.c.chainSelector, message);
+
+ assertEq(eth.c.token.balanceOf(address(eth.tokenPool)), tokenPoolBalance + amount);
+ assertEq(eth.c.token.balanceOf(alice), aliceBalance - amount);
+ assertEq(eth.tokenPool.getCurrentBridgedAmount(), bridgedAmount + amount);
+
+ // arb execute message
+ vm.selectFork(arb.c.forkId);
+
+ skip(_getInboundRefillTime(amount));
+ _refreshGasAndTokenPrices(arb.c, eth.c);
+ aliceBalance = arb.c.token.balanceOf(alice);
+ uint256 bucketLevel = arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel;
+
+ vm.expectEmit(address(arb.tokenPool));
+ emit Minted(address(arb.c.ethOffRamp), alice, amount);
+
+ vm.prank(address(arb.c.ethOffRamp));
+ arb.c.ethOffRamp.executeSingleMessage({
+ message: eventArg,
+ offchainTokenData: new bytes[](message.tokenAmounts.length),
+ tokenGasOverrides: new uint32[](0)
+ });
+
+ assertEq(arb.c.token.balanceOf(alice), aliceBalance + amount);
+ assertEq(
+ arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel,
+ bucketLevel + amount
+ );
+ }
+
+ // send amount back to eth
+ {
+ // send back from arb
+ vm.selectFork(arb.c.forkId);
+ vm.prank(alice);
+ arb.c.token.approve(address(arb.c.router), amount);
+ skip(_getOutboundRefillTime(amount));
+ _refreshGasAndTokenPrices(arb.c, eth.c);
+
+ uint256 aliceBalance = arb.c.token.balanceOf(alice);
+ uint256 bucketLevel = arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel;
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(CCIPSendParams({src: arb.c, dst: eth.c, sender: alice, amount: amount}));
+
+ vm.expectEmit(address(arb.tokenPool));
+ emit Burned(address(arb.c.ethOnRamp), amount);
+ vm.expectEmit(address(arb.c.ethOnRamp));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ arb.c.router.ccipSend{value: eventArg.feeTokenAmount}(eth.c.chainSelector, message);
+
+ assertEq(arb.c.token.balanceOf(alice), aliceBalance - amount);
+ assertEq(
+ arb.c.token.getFacilitator(address(arb.tokenPool)).bucketLevel,
+ bucketLevel - amount
+ );
+
+ // eth execute message
+ vm.selectFork(eth.c.forkId);
+
+ skip(_getInboundRefillTime(amount));
+ _refreshGasAndTokenPrices(eth.c, arb.c);
+ uint256 bridgedAmount = eth.tokenPool.getCurrentBridgedAmount();
+
+ vm.expectEmit(address(eth.tokenPool));
+ emit Released(address(eth.c.arbOffRamp), alice, amount);
+ vm.prank(address(eth.c.arbOffRamp));
+ eth.c.arbOffRamp.executeSingleMessage({
+ message: eventArg,
+ offchainTokenData: new bytes[](message.tokenAmounts.length),
+ tokenGasOverrides: new uint32[](0)
+ });
+
+ assertEq(eth.c.token.balanceOf(alice), amount);
+ assertEq(eth.tokenPool.getCurrentBridgedAmount(), bridgedAmount - amount);
+ }
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.sol
new file mode 100644
index 000000000..c43ce70ca
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.sol
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol';
+import {IUpgradeableLockReleaseTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+
+/**
+ * @title GHO Base Launch
+ * @author Aave Labs
+ * - Discussion: https://governance.aave.com/t/arfc-launch-gho-on-base-set-aci-as-emissions-manager-for-rewards/19338
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x03dc21e0423c60082dc23317af6ebaf997610cbc2cbb0f5a385653bd99a524fe
+ */
+contract AaveV3Ethereum_GHOBaseLaunch_20241223 is IProposalGenericExecutor {
+ uint64 public constant BASE_CHAIN_SELECTOR = 15971525489660198786;
+
+ // https://etherscan.io/address/0x06179f7C1be40863405f374E7f5F8806c728660A
+ IUpgradeableLockReleaseTokenPool_1_5_1 public constant TOKEN_POOL =
+ IUpgradeableLockReleaseTokenPool_1_5_1(0x06179f7C1be40863405f374E7f5F8806c728660A);
+
+ // https://basescan.org/address/0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34
+ address public constant REMOTE_TOKEN_POOL_BASE = 0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34;
+ // https://basescan.org/address/0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee
+ address public constant REMOTE_GHO_TOKEN_BASE = 0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee;
+
+ // Token Rate Limit Capacity: 300_000 GHO
+ uint128 public constant CCIP_RATE_LIMIT_CAPACITY = 300_000e18;
+ // Token Rate Limit Refill Rate: 60 GHO per second (=> 216_000 GHO per hour)
+ uint128 public constant CCIP_RATE_LIMIT_REFILL_RATE = 60e18;
+
+ function execute() external {
+ IRateLimiter.Config memory rateLimiterConfig = IRateLimiter.Config({
+ isEnabled: true,
+ capacity: CCIP_RATE_LIMIT_CAPACITY,
+ rate: CCIP_RATE_LIMIT_REFILL_RATE
+ });
+
+ IUpgradeableLockReleaseTokenPool_1_5_1.ChainUpdate[]
+ memory chainsToAdd = new IUpgradeableLockReleaseTokenPool_1_5_1.ChainUpdate[](1);
+
+ bytes[] memory remotePoolAddresses = new bytes[](1);
+ remotePoolAddresses[0] = abi.encode(REMOTE_TOKEN_POOL_BASE);
+
+ chainsToAdd[0] = IUpgradeableLockReleaseTokenPool_1_5_1.ChainUpdate({
+ remoteChainSelector: BASE_CHAIN_SELECTOR,
+ remotePoolAddresses: remotePoolAddresses,
+ remoteTokenAddress: abi.encode(REMOTE_GHO_TOKEN_BASE),
+ outboundRateLimiterConfig: rateLimiterConfig,
+ inboundRateLimiterConfig: rateLimiterConfig
+ });
+
+ TOKEN_POOL.applyChainUpdates({
+ remoteChainSelectorsToRemove: new uint64[](0),
+ chainsToAdd: chainsToAdd
+ });
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.t.sol b/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.t.sol
new file mode 100644
index 000000000..2271b0c66
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.t.sol
@@ -0,0 +1,468 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import 'forge-std/Test.sol';
+
+import {IUpgradeableLockReleaseTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol';
+import {IPool as IPool_CCIP} from 'src/interfaces/ccip/tokenPool/IPool.sol';
+import {IClient} from 'src/interfaces/ccip/IClient.sol';
+import {IInternal} from 'src/interfaces/ccip/IInternal.sol';
+import {IRouter} from 'src/interfaces/ccip/IRouter.sol';
+import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol';
+import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol';
+import {IEVM2EVMOffRamp_1_5} from 'src/interfaces/ccip/IEVM2EVMOffRamp.sol';
+import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol';
+import {IGhoToken} from 'src/interfaces/IGhoToken.sol';
+import {IGhoCcipSteward} from 'src/interfaces/IGhoCcipSteward.sol';
+
+import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol';
+import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
+
+import {CCIPUtils} from './utils/CCIPUtils.sol';
+
+import {AaveV3Ethereum_GHOCCIP151Upgrade_20241209} from '../20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol';
+import {AaveV3Ethereum_GHOBaseLaunch_20241223} from './AaveV3Ethereum_GHOBaseLaunch_20241223.sol';
+
+/**
+ * @dev Test for AaveV3Ethereum_GHOBaseLaunch_20241223
+ * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.t.sol -vv
+ */
+contract AaveV3Ethereum_GHOBaseLaunch_20241223_Test is ProtocolV3TestBase {
+ struct CCIPSendParams {
+ address sender;
+ uint256 amount;
+ uint64 destChainSelector;
+ }
+
+ uint64 internal constant ARB_CHAIN_SELECTOR = CCIPUtils.ARB_CHAIN_SELECTOR;
+ uint64 internal constant BASE_CHAIN_SELECTOR = CCIPUtils.BASE_CHAIN_SELECTOR;
+ uint64 internal constant ETH_CHAIN_SELECTOR = CCIPUtils.ETH_CHAIN_SELECTOR;
+ uint256 internal constant CCIP_RATE_LIMIT_CAPACITY = 300_000e18;
+ uint256 internal constant CCIP_RATE_LIMIT_REFILL_RATE = 60e18;
+
+ IGhoToken internal constant GHO = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING);
+ ITokenAdminRegistry internal constant TOKEN_ADMIN_REGISTRY =
+ ITokenAdminRegistry(0xb22764f98dD05c789929716D677382Df22C05Cb6);
+ IEVM2EVMOnRamp internal constant ARB_ON_RAMP =
+ IEVM2EVMOnRamp(0x69eCC4E2D8ea56E2d0a05bF57f4Fd6aEE7f2c284);
+ IEVM2EVMOnRamp internal constant BASE_ON_RAMP =
+ IEVM2EVMOnRamp(0xb8a882f3B88bd52D1Ff56A873bfDB84b70431937);
+ IEVM2EVMOffRamp_1_5 internal constant ARB_OFF_RAMP =
+ IEVM2EVMOffRamp_1_5(0xdf615eF8D4C64d0ED8Fd7824BBEd2f6a10245aC9);
+ IEVM2EVMOffRamp_1_5 internal constant BASE_OFF_RAMP =
+ IEVM2EVMOffRamp_1_5(0x6B4B6359Dd5B47Cdb030E5921456D2a0625a9EbD);
+
+ address public constant NEW_REMOTE_TOKEN_BASE = 0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee;
+ address internal constant NEW_REMOTE_POOL_ARB = 0xB94Ab28c6869466a46a42abA834ca2B3cECCA5eB;
+ address internal constant NEW_REMOTE_POOL_BASE = 0x98217A06721Ebf727f2C8d9aD7718ec28b7aAe34;
+ address internal constant RISK_COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60; // common across all chains
+ IRouter internal constant ROUTER = IRouter(0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D);
+ IGhoCcipSteward internal constant NEW_GHO_CCIP_STEWARD =
+ IGhoCcipSteward(0xC5BcC58BE6172769ca1a78B8A45752E3C5059c39);
+ IUpgradeableLockReleaseTokenPool_1_5_1 internal constant NEW_TOKEN_POOL =
+ IUpgradeableLockReleaseTokenPool_1_5_1(0x06179f7C1be40863405f374E7f5F8806c728660A);
+
+ AaveV3Ethereum_GHOBaseLaunch_20241223 internal proposal;
+
+ address internal alice = makeAddr('alice');
+ address internal bob = makeAddr('bob');
+ address internal carol = makeAddr('carol');
+
+ event Locked(address indexed sender, uint256 amount);
+ event Released(address indexed sender, address indexed recipient, uint256 amount);
+ event CCIPSendRequested(IInternal.EVM2EVMMessage message);
+
+ error CallerIsNotARampOnRouter(address);
+ error InvalidSourcePoolAddress(bytes);
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 21686002);
+
+ // pre-requisite, to be removed after execution
+ executePayload(vm, address(new AaveV3Ethereum_GHOCCIP151Upgrade_20241209()));
+
+ proposal = new AaveV3Ethereum_GHOBaseLaunch_20241223();
+ _validateConstants();
+
+ executePayload(vm, address(proposal));
+ }
+
+ function _validateConstants() private view {
+ assertEq(proposal.BASE_CHAIN_SELECTOR(), BASE_CHAIN_SELECTOR);
+ assertEq(address(proposal.TOKEN_POOL()), address(NEW_TOKEN_POOL));
+ assertEq(proposal.REMOTE_TOKEN_POOL_BASE(), NEW_REMOTE_POOL_BASE);
+ assertEq(proposal.REMOTE_GHO_TOKEN_BASE(), NEW_REMOTE_TOKEN_BASE);
+ assertEq(proposal.CCIP_RATE_LIMIT_CAPACITY(), CCIP_RATE_LIMIT_CAPACITY);
+ assertEq(proposal.CCIP_RATE_LIMIT_REFILL_RATE(), CCIP_RATE_LIMIT_REFILL_RATE);
+
+ assertEq(TOKEN_ADMIN_REGISTRY.typeAndVersion(), 'TokenAdminRegistry 1.5.0');
+ assertEq(NEW_TOKEN_POOL.typeAndVersion(), 'LockReleaseTokenPool 1.5.1');
+ assertEq(ROUTER.typeAndVersion(), 'Router 1.2.0');
+
+ _assertOnRamp(ARB_ON_RAMP, ETH_CHAIN_SELECTOR, ARB_CHAIN_SELECTOR, ROUTER);
+ _assertOnRamp(BASE_ON_RAMP, ETH_CHAIN_SELECTOR, BASE_CHAIN_SELECTOR, ROUTER);
+ _assertOffRamp(ARB_OFF_RAMP, ARB_CHAIN_SELECTOR, ETH_CHAIN_SELECTOR, ROUTER);
+ _assertOffRamp(BASE_OFF_RAMP, BASE_CHAIN_SELECTOR, ETH_CHAIN_SELECTOR, ROUTER);
+
+ assertEq(NEW_GHO_CCIP_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);
+ assertEq(NEW_GHO_CCIP_STEWARD.GHO_TOKEN(), AaveV3EthereumAssets.GHO_UNDERLYING);
+ assertEq(NEW_GHO_CCIP_STEWARD.GHO_TOKEN_POOL(), address(NEW_TOKEN_POOL));
+ assertTrue(NEW_GHO_CCIP_STEWARD.BRIDGE_LIMIT_ENABLED());
+ }
+
+ function _assertOnRamp(
+ IEVM2EVMOnRamp onRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(onRamp.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0');
+ assertEq(onRamp.getStaticConfig().chainSelector, srcSelector);
+ assertEq(onRamp.getStaticConfig().destChainSelector, dstSelector);
+ assertEq(onRamp.getDynamicConfig().router, address(router));
+ assertEq(router.getOnRamp(dstSelector), address(onRamp));
+ }
+
+ function _assertOffRamp(
+ IEVM2EVMOffRamp_1_5 offRamp,
+ uint64 srcSelector,
+ uint64 dstSelector,
+ IRouter router
+ ) internal view {
+ assertEq(offRamp.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0');
+ assertEq(offRamp.getStaticConfig().sourceChainSelector, srcSelector);
+ assertEq(offRamp.getStaticConfig().chainSelector, dstSelector);
+ assertEq(offRamp.getDynamicConfig().router, address(router));
+ assertTrue(router.isOffRamp(srcSelector, address(offRamp)));
+ }
+
+ function _getTokenMessage(
+ CCIPSendParams memory params
+ ) internal returns (IClient.EVM2AnyMessage memory, IInternal.EVM2EVMMessage memory) {
+ IClient.EVM2AnyMessage memory message = CCIPUtils.generateMessage(params.sender, 1);
+ message.tokenAmounts[0] = IClient.EVMTokenAmount({token: address(GHO), amount: params.amount});
+
+ uint256 feeAmount = ROUTER.getFee(params.destChainSelector, message);
+ deal(params.sender, feeAmount);
+
+ IInternal.EVM2EVMMessage memory eventArg = CCIPUtils.messageToEvent(
+ CCIPUtils.MessageToEventParams({
+ message: message,
+ router: ROUTER,
+ sourceChainSelector: ETH_CHAIN_SELECTOR,
+ destChainSelector: params.destChainSelector,
+ feeTokenAmount: feeAmount,
+ originalSender: params.sender,
+ sourceToken: address(GHO),
+ destinationToken: address(
+ params.destChainSelector == BASE_CHAIN_SELECTOR
+ ? NEW_REMOTE_TOKEN_BASE
+ : AaveV3ArbitrumAssets.GHO_UNDERLYING
+ )
+ })
+ );
+
+ return (message, eventArg);
+ }
+
+ function _tokenBucketToConfig(
+ IRateLimiter.TokenBucket memory bucket
+ ) internal pure returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: bucket.isEnabled,
+ capacity: bucket.capacity,
+ rate: bucket.rate
+ });
+ }
+
+ function _getDisabledConfig() internal pure returns (IRateLimiter.Config memory) {
+ return IRateLimiter.Config({isEnabled: false, capacity: 0, rate: 0});
+ }
+
+ function _getImplementation(address proxy) internal view returns (address) {
+ bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
+ return address(uint160(uint256(vm.load(proxy, slot))));
+ }
+
+ function _readInitialized(address proxy) internal view returns (uint8) {
+ return uint8(uint256(vm.load(proxy, bytes32(0))));
+ }
+
+ function _getRateLimiterConfig() internal view returns (IRateLimiter.Config memory) {
+ return
+ IRateLimiter.Config({
+ isEnabled: true,
+ capacity: proposal.CCIP_RATE_LIMIT_CAPACITY(),
+ rate: proposal.CCIP_RATE_LIMIT_REFILL_RATE()
+ });
+ }
+
+ function _getOutboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return (amount / CCIP_RATE_LIMIT_REFILL_RATE) + 1; // account for rounding
+ }
+
+ function _getInboundRefillTime(uint256 amount) internal pure returns (uint256) {
+ return amount / CCIP_RATE_LIMIT_REFILL_RATE + 1; // account for rounding
+ }
+
+ function _min(uint256 a, uint256 b) internal pure returns (uint256) {
+ return a < b ? a : b;
+ }
+
+ function assertEq(
+ IRateLimiter.TokenBucket memory bucket,
+ IRateLimiter.Config memory config
+ ) internal pure {
+ assertEq(bucket.isEnabled, config.isEnabled);
+ assertEq(bucket.capacity, config.capacity);
+ assertEq(bucket.rate, config.rate);
+ }
+
+ function test_basePoolConfig() public view {
+ assertEq(NEW_TOKEN_POOL.getSupportedChains().length, 2);
+ assertEq(NEW_TOKEN_POOL.getSupportedChains()[0], ARB_CHAIN_SELECTOR);
+ assertEq(NEW_TOKEN_POOL.getSupportedChains()[1], BASE_CHAIN_SELECTOR);
+ assertEq(
+ NEW_TOKEN_POOL.getRemoteToken(ARB_CHAIN_SELECTOR),
+ abi.encode(address(AaveV3ArbitrumAssets.GHO_UNDERLYING))
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getRemoteToken(BASE_CHAIN_SELECTOR),
+ abi.encode(address(NEW_REMOTE_TOKEN_BASE))
+ );
+ assertEq(NEW_TOKEN_POOL.getRemotePools(BASE_CHAIN_SELECTOR).length, 1);
+ assertEq(
+ NEW_TOKEN_POOL.getRemotePools(BASE_CHAIN_SELECTOR)[0],
+ abi.encode(address(NEW_REMOTE_POOL_BASE))
+ );
+ assertEq(NEW_TOKEN_POOL.getRemotePools(ARB_CHAIN_SELECTOR).length, 2);
+ assertEq(
+ NEW_TOKEN_POOL.getRemotePools(ARB_CHAIN_SELECTOR)[1], // 0th is the 1.4 token pool
+ abi.encode(address(NEW_REMOTE_POOL_ARB))
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(ARB_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(ARB_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(BASE_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ assertEq(
+ NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(BASE_CHAIN_SELECTOR),
+ _getRateLimiterConfig()
+ );
+ }
+
+ function test_sendMessageToBaseSucceeds(uint256 amount) public {
+ uint256 bridgeableAmount = _min(
+ NEW_TOKEN_POOL.getBridgeLimit() - NEW_TOKEN_POOL.getCurrentBridgedAmount(),
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getOutboundRefillTime(amount)); // wait for the rate limiter to refill
+
+ deal(address(GHO), alice, amount);
+ vm.prank(alice);
+ GHO.approve(address(ROUTER), amount);
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 currentBridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount();
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({amount: amount, sender: alice, destChainSelector: BASE_CHAIN_SELECTOR})
+ );
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Locked(address(BASE_ON_RAMP), amount);
+ vm.expectEmit(address(BASE_ON_RAMP));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ ROUTER.ccipSend{value: eventArg.feeTokenAmount}(BASE_CHAIN_SELECTOR, message);
+
+ assertEq(GHO.balanceOf(alice), aliceBalance - amount);
+ assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), currentBridgedAmount + amount);
+ }
+
+ function test_sendMessageToArbSucceeds(uint256 amount) public {
+ uint256 bridgeableAmount = _min(
+ NEW_TOKEN_POOL.getBridgeLimit() - NEW_TOKEN_POOL.getCurrentBridgedAmount(),
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getOutboundRefillTime(amount)); // wait for the rate limiter to refill
+
+ deal(address(GHO), alice, amount);
+ vm.prank(alice);
+ GHO.approve(address(ROUTER), amount);
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 currentBridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount();
+
+ (
+ IClient.EVM2AnyMessage memory message,
+ IInternal.EVM2EVMMessage memory eventArg
+ ) = _getTokenMessage(
+ CCIPSendParams({amount: amount, sender: alice, destChainSelector: ARB_CHAIN_SELECTOR})
+ );
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Locked(address(ARB_ON_RAMP), amount);
+ vm.expectEmit(address(ARB_ON_RAMP));
+ emit CCIPSendRequested(eventArg);
+
+ vm.prank(alice);
+ ROUTER.ccipSend{value: eventArg.feeTokenAmount}(ARB_CHAIN_SELECTOR, message);
+
+ assertEq(GHO.balanceOf(alice), aliceBalance - amount);
+ assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), currentBridgedAmount + amount);
+ }
+
+ function test_offRampViaBaseSucceeds(uint256 amount) public {
+ uint256 bridgeableAmount = _min(
+ NEW_TOKEN_POOL.getCurrentBridgedAmount(),
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getInboundRefillTime(amount));
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 poolBalance = GHO.balanceOf(address(NEW_TOKEN_POOL));
+ uint256 currentBridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount();
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Released(address(BASE_OFF_RAMP), alice, amount);
+
+ vm.prank(address(BASE_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: BASE_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_BASE)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), poolBalance - amount);
+ assertEq(GHO.balanceOf(alice), aliceBalance + amount);
+ assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), currentBridgedAmount - amount);
+ assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), GHO.balanceOf(address(NEW_TOKEN_POOL)));
+ }
+
+ function test_offRampViaArbSucceeds(uint256 amount) public {
+ uint256 bridgeableAmount = _min(
+ NEW_TOKEN_POOL.getCurrentBridgedAmount(),
+ CCIP_RATE_LIMIT_CAPACITY
+ );
+ amount = bound(amount, 1, bridgeableAmount);
+ skip(_getInboundRefillTime(amount));
+
+ uint256 aliceBalance = GHO.balanceOf(alice);
+ uint256 poolBalance = GHO.balanceOf(address(NEW_TOKEN_POOL));
+ uint256 currentBridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount();
+
+ vm.expectEmit(address(NEW_TOKEN_POOL));
+ emit Released(address(ARB_OFF_RAMP), alice, amount);
+
+ vm.prank(address(ARB_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ARB_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ARB)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), poolBalance - amount);
+ assertEq(GHO.balanceOf(alice), aliceBalance + amount);
+ assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), currentBridgedAmount - amount);
+ assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), GHO.balanceOf(address(NEW_TOKEN_POOL)));
+ }
+
+ function test_cannotUseBaseOffRampForArbMessages() public {
+ uint256 amount = 100e18;
+ skip(_getInboundRefillTime(amount));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(CallerIsNotARampOnRouter.selector, address(BASE_OFF_RAMP))
+ );
+ vm.prank(address(BASE_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ARB_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ARB)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+ }
+
+ function test_cannotOffRampOtherChainMessages() public {
+ uint256 amount = 100e18;
+ skip(_getInboundRefillTime(amount));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ InvalidSourcePoolAddress.selector,
+ abi.encode(address(NEW_REMOTE_POOL_ARB))
+ )
+ );
+ vm.prank(address(BASE_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: BASE_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ARB)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ InvalidSourcePoolAddress.selector,
+ abi.encode(address(NEW_REMOTE_POOL_BASE))
+ )
+ );
+ vm.prank(address(ARB_OFF_RAMP));
+ NEW_TOKEN_POOL.releaseOrMint(
+ IPool_CCIP.ReleaseOrMintInV1({
+ originalSender: abi.encode(alice),
+ remoteChainSelector: ARB_CHAIN_SELECTOR,
+ receiver: alice,
+ amount: amount,
+ localToken: address(GHO),
+ sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_BASE)),
+ sourcePoolData: new bytes(0),
+ offchainTokenData: new bytes(0)
+ })
+ );
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch.md b/src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch.md
new file mode 100644
index 000000000..e330a43a3
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch.md
@@ -0,0 +1,62 @@
+---
+title: "GHO Base Launch"
+author: "Aave Labs"
+discussions: "https://governance.aave.com/t/arfc-launch-gho-on-base-set-aci-as-emissions-manager-for-rewards/19338"
+---
+
+## Simple Summary
+
+This AIP proposes the expansion of GHO, the native asset of the Aave Protocol, to the Base network utilizing the Chainlink Cross-Chain Interoperability Protocol (CCIP) v1.5.1.
+
+The smart contracts have been refined through multiple stages of design, development, testing, and implementation. Likewise, Certora, the DAO service provider, was engaged to conduct code reviews of the implementation.
+
+## Motivation
+
+Building on the successful expansion of GHO into Arbitrum, it is now time to expand GHO to other networks. The Base ecosystem will bring a new set of opportunities, allowing access to a wide array of integrations with other protocols and tools and ultimately enriching GHO's utility potential.
+
+## Specification
+
+This AIP includes a series of actions required to launch GHO on Base:
+
+1. Configure new Chainlink CCIP lanes between Base and Ethereum/Arbitrum (while retaining existing ones) with a rate limit of 300,000 GHO capacity and 60 GHO per second rate.
+2. Update the proxy admin of GHO token on Arbitrum to OpenZeppelin v5.1 Proxy Contract, enabling GHO on Arbitrum to be aligned with Base deployment.
+3. Configure and activate GhoAaveSteward and GhoCcipSteward to control GHO listing and CCIP lane.
+4. List GHO as a borrowable asset on the Aave Pool, with the risk configuration specified in the ARFC. Then, provide initial liquidity to the pool as a security measure to mitigate potential vulnerabilities and facilitate a stable launch.
+5. Set ACI multisig as Emissions Manager for GHO and aGHO rewards, as specified in the ARFC.
+
+The table below illustrates the configured risk parameters for **GHO**
+
+| Parameter | Value |
+| ------------------------- | -----------------------------------------: |
+| Isolation Mode | false |
+| Borrowable | ENABLED |
+| Collateral Enabled | false |
+| Supply Cap (BLT) | 2,500,000 |
+| Borrow Cap (BLT) | 2,250,000 |
+| Debt Ceiling | USD 0 |
+| LTV | 0 % |
+| LT | 0 % |
+| Liquidation Bonus | 0 % |
+| Liquidation Protocol Fee | 0 % |
+| Reserve Factor | 10 % |
+| Base Variable Borrow Rate | 0 % |
+| Variable Slope 1 | 12 % |
+| Variable Slope 2 | 65 % |
+| Uoptimal | 90 % |
+| Flashloanable | ENABLED |
+| Siloed Borrowing | DISABLED |
+| Borrowable in Isolation | DISABLED |
+| Oracle | 0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73 |
+
+Additionaly [0xac140648435d03f784879cd789130F22Ef588Fcd](https://basescan.org/address/0xac140648435d03f784879cd789130F22Ef588Fcd) has been set as the emission admin for GHO and the corresponding aToken.
+
+## References
+
+- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.sol), [AaveV3Arbitrum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.sol), [AaveV3BaseLaunch](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.sol), [AaveV3BaseListing](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.sol)
+- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Ethereum_GHOBaseLaunch_20241223.t.sol), [AaveV3Arbitrum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Arbitrum_GHOBaseLaunch_20241223.t.sol), [AaveV3BaseLaunch](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseLaunch_20241223.t.sol), [AaveV3BaseListing](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3Base_GHOBaseListing_20241223.t.sol), [E2EFlow](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20241223_Multi_GHOBaseLaunch/AaveV3E2E_GHOBaseLaunch_20241223.t.sol)
+- [Snapshot](https://snapshot.box/#/s:aave.eth/proposal/0x03dc21e0423c60082dc23317af6ebaf997610cbc2cbb0f5a385653bd99a524fe)
+- [Discussion](https://governance.aave.com/t/arfc-launch-gho-on-base-set-aci-as-emissions-manager-for-rewards/19338)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch_20241223.s.sol b/src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch_20241223.s.sol
new file mode 100644
index 000000000..b954d900e
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch_20241223.s.sol
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript, ArbitrumScript, BaseScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3Ethereum_GHOBaseLaunch_20241223} from './AaveV3Ethereum_GHOBaseLaunch_20241223.sol';
+import {AaveV3Arbitrum_GHOBaseLaunch_20241223} from './AaveV3Arbitrum_GHOBaseLaunch_20241223.sol';
+import {AaveV3Base_GHOBaseLaunch_20241223} from './AaveV3Base_GHOBaseLaunch_20241223.sol';
+import {AaveV3Base_GHOBaseListing_20241223} from './AaveV3Base_GHOBaseListing_20241223.sol';
+/**
+ * @dev Deploy Ethereum
+ * deploy-command: make deploy-ledger contract=src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch_20241223.s.sol:DeployEthereum chain=mainnet
+ * verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/GHOBaseLaunch_20241223.s.sol/1/run-latest.json
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Ethereum_GHOBaseLaunch_20241223).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Deploy Arbitrum
+ * deploy-command: make deploy-ledger contract=src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch_20241223.s.sol:DeployArbitrum chain=arbitrum
+ * verify-command: FOUNDRY_PROFILE=arbitrum npx catapulta-verify -b broadcast/GHOBaseLaunch_20241223.s.sol/42161/run-latest.json
+ */
+contract DeployArbitrum is ArbitrumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Arbitrum_GHOBaseLaunch_20241223).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Deploy Base
+ * deploy-command: make deploy-ledger contract=src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch_20241223.s.sol:DeployBase chain=base
+ * verify-command: FOUNDRY_PROFILE=base npx catapulta-verify -b broadcast/GHOBaseLaunch_20241223.s.sol/8453/run-latest.json
+ */
+contract DeployBase is BaseScript {
+ function run() external broadcast {
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory launchActions = new IPayloadsControllerCore.ExecutionAction[](1);
+ launchActions[0] = GovV3Helpers.buildAction(
+ GovV3Helpers.deployDeterministic(type(AaveV3Base_GHOBaseLaunch_20241223).creationCode)
+ );
+
+ IPayloadsControllerCore.ExecutionAction[]
+ memory listingActions = new IPayloadsControllerCore.ExecutionAction[](1);
+ listingActions[0] = GovV3Helpers.buildAction(
+ GovV3Helpers.deployDeterministic(type(AaveV3Base_GHOBaseListing_20241223).creationCode)
+ );
+
+ // register both actions separately at payloadsController
+ GovV3Helpers.createPayload(launchActions);
+ GovV3Helpers.createPayload(listingActions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch_20241223.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](4);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(
+ type(AaveV3Ethereum_GHOBaseLaunch_20241223).creationCode
+ );
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsArbitrum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsArbitrum[0] = GovV3Helpers.buildAction(
+ type(AaveV3Arbitrum_GHOBaseLaunch_20241223).creationCode
+ );
+ payloads[1] = GovV3Helpers.buildArbitrumPayload(vm, actionsArbitrum);
+
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsBaseLaunch = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsBaseLaunch[0] = GovV3Helpers.buildAction(
+ type(AaveV3Base_GHOBaseLaunch_20241223).creationCode
+ );
+ payloads[2] = GovV3Helpers.buildBasePayload(vm, actionsBaseLaunch);
+
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsBaseListing = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsBaseListing[0] = GovV3Helpers.buildAction(
+ type(AaveV3Base_GHOBaseListing_20241223).creationCode
+ );
+ payloads[3] = GovV3Helpers.buildBasePayload(vm, actionsBaseListing);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(vm, 'src/20241223_Multi_GHOBaseLaunch/GHOBaseLaunch.md')
+ );
+ }
+}
diff --git a/src/20241223_Multi_GHOBaseLaunch/config.ts b/src/20241223_Multi_GHOBaseLaunch/config.ts
new file mode 100644
index 000000000..bccd88a15
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/config.ts
@@ -0,0 +1,51 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ pools: ['AaveV3Ethereum', 'AaveV3Arbitrum', 'AaveV3Base'],
+ title: 'Launch GHO on Base',
+ shortName: 'GHOBaseLaunch',
+ date: '20241223',
+ author: 'Aave Labs',
+ discussion:
+ 'https://governance.aave.com/t/arfc-launch-gho-on-base-set-aci-as-emissions-manager-for-rewards/19338',
+ snapshot:
+ 'https://snapshot.box/#/s:aave.eth/proposal/0x03dc21e0423c60082dc23317af6ebaf997610cbc2cbb0f5a385653bd99a524fe',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {
+ AaveV3Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 21686002}},
+ AaveV3Arbitrum: {configs: {OTHERS: {}}, cache: {blockNumber: 298375852}},
+ AaveV3Base: {
+ configs: {
+ ASSET_LISTING: [
+ {
+ assetSymbol: 'GHO',
+ decimals: 18,
+ priceFeed: '0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73',
+ ltv: '0',
+ liqThreshold: '0',
+ liqBonus: '0',
+ debtCeiling: '0',
+ liqProtocolFee: '0',
+ enabledToBorrow: 'ENABLED',
+ flashloanable: 'ENABLED',
+ borrowableInIsolation: 'DISABLED',
+ withSiloedBorrowing: 'DISABLED',
+ reserveFactor: '10',
+ supplyCap: '2500000',
+ borrowCap: '2250000',
+ rateStrategyParams: {
+ optimalUtilizationRate: '90',
+ baseVariableBorrowRate: '0',
+ variableRateSlope1: '12',
+ variableRateSlope2: '65',
+ },
+ asset: '0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee',
+ admin: '0xac140648435d03f784879cd789130F22Ef588Fcd',
+ },
+ ],
+ },
+ cache: {blockNumber: 25415842},
+ },
+ },
+};
diff --git a/src/20241223_Multi_GHOBaseLaunch/utils/CCIPUtils.sol b/src/20241223_Multi_GHOBaseLaunch/utils/CCIPUtils.sol
new file mode 100644
index 000000000..c3b489477
--- /dev/null
+++ b/src/20241223_Multi_GHOBaseLaunch/utils/CCIPUtils.sol
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {IClient} from 'src/interfaces/ccip/IClient.sol';
+import {IRouter} from 'src/interfaces/ccip/IRouter.sol';
+import {IInternal} from 'src/interfaces/ccip/IInternal.sol';
+import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol';
+
+library CCIPUtils {
+ uint64 internal constant ARB_CHAIN_SELECTOR = 4949039107694359620;
+ uint64 internal constant BASE_CHAIN_SELECTOR = 15971525489660198786;
+ uint64 internal constant ETH_CHAIN_SELECTOR = 5009297550715157269;
+
+ bytes32 internal constant LEAF_DOMAIN_SEPARATOR =
+ 0x0000000000000000000000000000000000000000000000000000000000000000;
+ bytes32 internal constant INTERNAL_DOMAIN_SEPARATOR =
+ 0x0000000000000000000000000000000000000000000000000000000000000001;
+ bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256('EVM2EVMMessageHashV2');
+ bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
+
+ struct SourceTokenData {
+ bytes sourcePoolAddress;
+ bytes destTokenAddress;
+ bytes extraData;
+ uint32 destGasAmount;
+ }
+
+ struct MessageToEventParams {
+ IClient.EVM2AnyMessage message;
+ IRouter router;
+ uint64 sourceChainSelector;
+ uint64 destChainSelector;
+ uint256 feeTokenAmount;
+ address originalSender;
+ address sourceToken;
+ address destinationToken;
+ }
+
+ function generateMessage(
+ address receiver,
+ uint256 tokenAmountsLength
+ ) internal pure returns (IClient.EVM2AnyMessage memory) {
+ return
+ IClient.EVM2AnyMessage({
+ receiver: abi.encode(receiver),
+ data: '',
+ tokenAmounts: new IClient.EVMTokenAmount[](tokenAmountsLength),
+ feeToken: address(0),
+ extraArgs: argsToBytes(IClient.EVMExtraArgsV1({gasLimit: 0}))
+ });
+ }
+
+ function messageToEvent(
+ MessageToEventParams memory params
+ ) public view returns (IInternal.EVM2EVMMessage memory) {
+ IEVM2EVMOnRamp onRamp = IEVM2EVMOnRamp(params.router.getOnRamp(params.destChainSelector));
+
+ bytes memory args = new bytes(params.message.extraArgs.length - 4);
+ for (uint256 i = 4; i < params.message.extraArgs.length; ++i) {
+ args[i - 4] = params.message.extraArgs[i];
+ }
+
+ IInternal.EVM2EVMMessage memory messageEvent = IInternal.EVM2EVMMessage({
+ sequenceNumber: onRamp.getExpectedNextSequenceNumber(),
+ feeTokenAmount: params.feeTokenAmount,
+ sender: params.originalSender,
+ nonce: onRamp.getSenderNonce(params.originalSender) + 1,
+ gasLimit: abi.decode(args, (IClient.EVMExtraArgsV1)).gasLimit,
+ strict: false,
+ sourceChainSelector: params.sourceChainSelector,
+ receiver: abi.decode(params.message.receiver, (address)),
+ data: params.message.data,
+ tokenAmounts: params.message.tokenAmounts,
+ sourceTokenData: new bytes[](params.message.tokenAmounts.length),
+ feeToken: params.router.getWrappedNative(),
+ messageId: ''
+ });
+
+ for (uint256 i; i < params.message.tokenAmounts.length; ++i) {
+ messageEvent.sourceTokenData[i] = abi.encode(
+ SourceTokenData({
+ sourcePoolAddress: abi.encode(
+ onRamp.getPoolBySourceToken(
+ params.destChainSelector,
+ params.message.tokenAmounts[i].token
+ )
+ ),
+ destTokenAddress: abi.encode(params.destinationToken),
+ extraData: abi.encode(getTokenDecimals(params.sourceToken)),
+ destGasAmount: getDestGasAmount(onRamp, params.message.tokenAmounts[i].token)
+ })
+ );
+ }
+
+ messageEvent.messageId = hash(
+ messageEvent,
+ generateMetadataHash(params.sourceChainSelector, params.destChainSelector, address(onRamp))
+ );
+ return messageEvent;
+ }
+
+ function generateMetadataHash(
+ uint64 sourceChainSelector,
+ uint64 destChainSelector,
+ address onRamp
+ ) internal pure returns (bytes32) {
+ return
+ keccak256(abi.encode(EVM_2_EVM_MESSAGE_HASH, sourceChainSelector, destChainSelector, onRamp));
+ }
+
+ function argsToBytes(
+ IClient.EVMExtraArgsV1 memory extraArgs
+ ) internal pure returns (bytes memory bts) {
+ return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
+ }
+
+ /// @dev Used to hash messages for single-lane ramps.
+ /// OnRamp hash(EVM2EVMMessage) = OffRamp hash(EVM2EVMMessage)
+ /// The EVM2EVMMessage's messageId is expected to be the output of this hash function
+ /// @param original Message to hash
+ /// @param metadataHash Immutable metadata hash representing a lane with a fixed OnRamp
+ /// @return hashedMessage hashed message as a keccak256
+ function hash(
+ IInternal.EVM2EVMMessage memory original,
+ bytes32 metadataHash
+ ) internal pure returns (bytes32) {
+ // Fixed-size message fields are included in nested hash to reduce stack pressure.
+ // This hashing scheme is also used by RMN. If changing it, please notify the RMN maintainers.
+ return
+ keccak256(
+ abi.encode(
+ LEAF_DOMAIN_SEPARATOR,
+ metadataHash,
+ keccak256(
+ abi.encode(
+ original.sender,
+ original.receiver,
+ original.sequenceNumber,
+ original.gasLimit,
+ original.strict,
+ original.nonce,
+ original.feeToken,
+ original.feeTokenAmount
+ )
+ ),
+ keccak256(original.data),
+ keccak256(abi.encode(original.tokenAmounts)),
+ keccak256(abi.encode(original.sourceTokenData))
+ )
+ );
+ }
+
+ function getDestGasAmount(IEVM2EVMOnRamp onRamp, address token) internal view returns (uint32) {
+ IEVM2EVMOnRamp.TokenTransferFeeConfig memory config = onRamp.getTokenTransferFeeConfig(token);
+ return
+ config.isEnabled
+ ? config.destGasOverhead
+ : onRamp.getDynamicConfig().defaultTokenDestGasOverhead;
+ }
+
+ function getTokenDecimals(address token) internal view returns (uint8) {
+ (bool success, bytes memory data) = token.staticcall(abi.encodeWithSignature('decimals()'));
+ require(success, 'CCIPUtils: failed to get token decimals');
+ return abi.decode(data, (uint8));
+ }
+}
diff --git a/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.sol b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.sol
new file mode 100644
index 000000000..7f6d207a9
--- /dev/null
+++ b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.sol
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {CollectorUtils, ICollector} from 'aave-helpers/src/CollectorUtils.sol';
+import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {AaveV3EthereumLido, AaveV3EthereumLidoAssets} from 'aave-address-book/AaveV3EthereumLido.sol';
+import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol';
+
+/**
+ * @title karpatkey Gho Growth Service Provider
+ * @author karpatkey
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x87585d9dcb104d2946ca2def6bcf57708480fafc5e310de4850dc2fbe1820893
+ * - Discussion: https://governance.aave.com/t/arfc-karpatkey-as-gho-growth-service-provider/20206
+ */
+contract AaveV3Ethereum_karpatkeyGhoGrowth_20241231 is IProposalGenericExecutor {
+ using CollectorUtils for ICollector;
+
+ uint256 public constant KARPATKEY_STREAM_AMOUNT = 250_000e18;
+ address public constant KARPATKEY_SAFE = 0x58e6c7ab55Aa9012eAccA16d1ED4c15795669E1C;
+ uint256 public constant STREAM_DURATION = 180 days;
+ uint256 public constant STREAM_START_TIME = 1734912000; // Sun Dec 23 2024 12:00 GMT+0000
+ uint256 public constant ACTUAL_STREAM_AMOUNT =
+ (KARPATKEY_STREAM_AMOUNT / STREAM_DURATION) * STREAM_DURATION;
+
+ function execute() external override {
+ uint256 backDatedAmount = (ACTUAL_STREAM_AMOUNT * (block.timestamp - STREAM_START_TIME)) /
+ STREAM_DURATION;
+
+ // transfer backend amount
+ AaveV3Ethereum.COLLECTOR.transfer(
+ AaveV3EthereumLidoAssets.GHO_A_TOKEN,
+ KARPATKEY_SAFE,
+ backDatedAmount
+ );
+
+ // stream
+ AaveV3Ethereum.COLLECTOR.stream(
+ CollectorUtils.CreateStreamInput({
+ underlying: AaveV3EthereumLidoAssets.GHO_A_TOKEN,
+ receiver: KARPATKEY_SAFE,
+ amount: ACTUAL_STREAM_AMOUNT - backDatedAmount,
+ start: block.timestamp,
+ duration: STREAM_DURATION + STREAM_START_TIME - block.timestamp
+ })
+ );
+ }
+}
diff --git a/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.t.sol b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.t.sol
new file mode 100644
index 000000000..a75c2aa03
--- /dev/null
+++ b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.t.sol
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {AaveV3EthereumLido, AaveV3EthereumLidoAssets} from 'aave-address-book/AaveV3EthereumLido.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Ethereum_karpatkeyGhoGrowth_20241231} from './AaveV3Ethereum_karpatkeyGhoGrowth_20241231.sol';
+
+/**
+ * @dev Test for AaveV3Ethereum_karpatkeyGhoGrowth_20241231
+ * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.t.sol -vv
+ */
+contract AaveV3Ethereum_karpatkeyGhoGrowth_20241231_Test is ProtocolV3TestBase {
+ AaveV3Ethereum_karpatkeyGhoGrowth_20241231 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 21524445);
+ proposal = new AaveV3Ethereum_karpatkeyGhoGrowth_20241231();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3Ethereum_karpatkeyGhoGrowth_20241231',
+ AaveV3Ethereum.POOL,
+ address(proposal)
+ );
+ }
+
+ function test_stream() public {
+ uint256 backDatedAmount = (proposal.ACTUAL_STREAM_AMOUNT() *
+ (block.timestamp - proposal.STREAM_START_TIME())) / proposal.STREAM_DURATION();
+
+ uint256 nextCollectorStreamID = AaveV3Ethereum.COLLECTOR.getNextStreamId();
+
+ executePayload(vm, address(proposal));
+
+ (
+ address sender,
+ address recipient,
+ uint256 deposit,
+ address tokenAddress,
+ uint256 startTime,
+ uint256 stopTime,
+ uint256 remainingBalance,
+
+ ) = AaveV3Ethereum.COLLECTOR.getStream(nextCollectorStreamID);
+
+ assertEq(sender, address(AaveV3Ethereum.COLLECTOR));
+ assertEq(recipient, proposal.KARPATKEY_SAFE());
+ assertEq(deposit, proposal.ACTUAL_STREAM_AMOUNT() - backDatedAmount);
+ assertEq(tokenAddress, AaveV3EthereumLidoAssets.GHO_A_TOKEN);
+ assertEq(startTime, block.timestamp);
+ assertEq(stopTime, proposal.STREAM_START_TIME() + proposal.STREAM_DURATION());
+ assertEq(remainingBalance, proposal.ACTUAL_STREAM_AMOUNT() - backDatedAmount);
+
+ // Can withdraw during stream
+ vm.warp(block.timestamp + 30 days);
+
+ uint256 collectorGhoBalanceBefore = IERC20(AaveV3EthereumLidoAssets.GHO_A_TOKEN).balanceOf(
+ address(AaveV3Ethereum.COLLECTOR)
+ );
+ uint256 receiverGhoBalanceBefore = IERC20(AaveV3EthereumLidoAssets.GHO_A_TOKEN).balanceOf(
+ proposal.KARPATKEY_SAFE()
+ );
+
+ vm.startPrank(proposal.KARPATKEY_SAFE());
+ AaveV3Ethereum.COLLECTOR.withdrawFromStream(
+ nextCollectorStreamID,
+ proposal.ACTUAL_STREAM_AMOUNT() / 9
+ );
+ vm.stopPrank();
+
+ assertGt(
+ IERC20(AaveV3EthereumLidoAssets.GHO_A_TOKEN).balanceOf(proposal.KARPATKEY_SAFE()),
+ receiverGhoBalanceBefore
+ );
+
+ assertLt(
+ IERC20(AaveV3EthereumLidoAssets.GHO_A_TOKEN).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
+ collectorGhoBalanceBefore
+ );
+
+ // Can withdraw post stream all remaining funds
+ vm.warp(block.timestamp + proposal.STREAM_DURATION());
+
+ (, , , , , , uint256 remaining, ) = AaveV3Ethereum.COLLECTOR.getStream(nextCollectorStreamID);
+
+ vm.startPrank(proposal.KARPATKEY_SAFE());
+ AaveV3Ethereum.COLLECTOR.withdrawFromStream(nextCollectorStreamID, remaining);
+ vm.stopPrank();
+
+ assertEq(
+ IERC20(AaveV3EthereumLidoAssets.GHO_A_TOKEN).balanceOf(proposal.KARPATKEY_SAFE()),
+ receiverGhoBalanceBefore + proposal.ACTUAL_STREAM_AMOUNT() - backDatedAmount
+ );
+ }
+}
diff --git a/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/config.ts b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/config.ts
new file mode 100644
index 000000000..9d3c60ec1
--- /dev/null
+++ b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/config.ts
@@ -0,0 +1,16 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ pools: ['AaveV3Ethereum'],
+ title: 'karpatkey Gho Growth Service Provider',
+ shortName: 'karpatkeyGhoGrowthServiceProvider',
+ date: '20241231',
+ author: 'karpatkey',
+ discussion:
+ ' https://governance.aave.com/t/arfc-karpatkey-as-gho-growth-service-provider/20206',
+ snapshot:
+ 'https://snapshot.box/#/s:aave.eth/proposal/0x87585d9dcb104d2946ca2def6bcf57708480fafc5e310de4850dc2fbe1820893',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {AaveV3Ethereum: {configs: {}, cache: {blockNumber: 21524445}}},
+};
diff --git a/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/karpatkeyGhoGrowth.md b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/karpatkeyGhoGrowth.md
new file mode 100644
index 000000000..cf1afba78
--- /dev/null
+++ b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/karpatkeyGhoGrowth.md
@@ -0,0 +1,48 @@
+---
+title: "Funding Proposal: karpatkey as GHO Growth Service Provider"
+author: "karpatkey"
+discussions: https://governance.aave.com/t/arfc-karpatkey-as-gho-growth-service-provider/20206
+snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x87585d9dcb104d2946ca2def6bcf57708480fafc5e310de4850dc2fbe1820893
+---
+
+## Simple Summary
+
+The AIP proposes appointing karpatkey as a service provider dedicated to drive the growth of GHO for the following 6 months, with an initial focus on opportunities within the Gnosis Chain while also expanding efforts to other chains.
+
+## Motivation
+
+Over the past 12 months, karpatkey has been pleased to have supported the AaveDAO in improving treasury management practices and GHO adoption (see Phase 1 and Phase 2 for more details). These efforts have been a significant success, and will continue, with the deployment of the Finance Steward upcoming features. That said, by focusing on increasing GHO’s adoption, we believe karpatkey can provide more value to the Aave ecosystem.
+
+The stablecoin ecosystem is one of the most dynamic and competitive sectors in the DeFi landscape, and the accelerating adoption of GHO represents a unique opportunity for Aave DAO to solidify its position as a leader in this space. Since its deployment over a year ago, GHO achieved a notable milestone of a 180M supply across mainnet and Arbitrum, market depth in most reputable DEXes with total liquidity ranging between $30M and $50M. This success is a testament to the strength of Aave’s products, user base and brand.
+
+While GHO’s initial growth has been remarkable, the stablecoin market is evolving rapidly. The demand for reliable, decentralised stablecoins continues to grow, and GHO is well-positioned to capture this market—provided we continue to expand its utility, adoption, and ecosystem integrations proactively.
+
+karpatkey has the tools and resources to capitalise on the range of opportunities GHO presents. With a proven track record in DeFi innovation and strong partnerships with other DAOs, we are excited to present our new 6-month proposal to Aave DAO, focused on the areas where we can contribute most.
+
+To drive proactive growth and expansion of GHO across multiple blockchains, we are proposing a phased mandate that includes tailored strategies for each ecosystem, with the initial phase prioritizing the deployment and adoption of GHO on the Gnosis Chain. With an Aave instance holding $100M, Gnosis Chain currently has a total value locked of $410M and a thriving environment of stablecoins, which includes Gnosis Pay, Monerium’s e-money EURe and other RWAs. We plan to focus primarily on opportunities where both AaveDAO and GnosisDAO can mutually benefit from a vibrant GHO ecosystem on Gnosis Chain, taking advantage of karpatkey’s unique position supporting both the DeFi ecosystems of both DAOs.
+
+## Terms
+
+- 6-month engagement, December 23th 2024 to June 21th 2025;
+- $250k, streamed linearly throughout the engagement;
+
+## Specification
+
+Create the following stream allowing TokenLogic to withdraw aEthLidoGHO from the Prime instance.
+
+- Recipient: karpatkey
+- Stream: 250K aEthLidoGHO over 180 days
+- Address: `0x58e6c7ab55aa9012eacca16d1ed4c15795669e1c`
+
+The stream shall commence the next block from when the previous stream finishes.
+
+## References
+
+- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/57aaaa1bfa341a98ac5fbb549aa16941eef69ed7/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.sol)
+- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/57aaaa1bfa341a98ac5fbb549aa16941eef69ed7/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.t.sol)
+ [Snapshot](https://snapshot.box/#/s:aave.eth/proposal/0x87585d9dcb104d2946ca2def6bcf57708480fafc5e310de4850dc2fbe1820893)
+- [Discussion](https://governance.aave.com/t/arfc-karpatkey-as-gho-growth-service-provider/20206)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/karpatkeyGhoGrowth_20241231.s.sol b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/karpatkeyGhoGrowth_20241231.s.sol
new file mode 100644
index 000000000..f9544095c
--- /dev/null
+++ b/src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/karpatkeyGhoGrowth_20241231.s.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3Ethereum_karpatkeyGhoGrowth_20241231} from './AaveV3Ethereum_karpatkeyGhoGrowth_20241231.sol';
+
+/**
+ * @dev Deploy Ethereum
+ * deploy-command: make deploy-ledger contract=src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.s.sol:DeployEthereum chain=mainnet
+ * verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.s.sol/1/run-latest.json
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Ethereum_karpatkeyGhoGrowth_20241231).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/AaveV3Ethereum_karpatkeyGhoGrowth_20241231.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(
+ type(AaveV3Ethereum_karpatkeyGhoGrowth_20241231).creationCode
+ );
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20241231_AaveV3Ethereum_karpatkeyGhoGrowth/karpatkeyGhoGrowth.md'
+ )
+ );
+ }
+}
diff --git a/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding.md b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding.md
new file mode 100644
index 000000000..dc5b78349
--- /dev/null
+++ b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding.md
@@ -0,0 +1,45 @@
+---
+title: "Aave v3.3 Sherlock contest funding"
+author: "BGD Labs @bgdlabs"
+discussions: "https://governance.aave.com/t/arfc-bgd-aave-v3-3-sherlock-contest/20498"
+snapshot: "https://snapshot.box/#/s:aave.eth/proposal/0x8c04404012d9b74c3e7cebff2ddff3c9d40a280b4cfa7c2fca42be2a59b005ee"
+---
+
+## Simple Summary
+
+Proposal for the Aave DAO to host a Sherlock contest for the upcoming [Aave v3.3 upgrade](https://github.com/aave-dao/aave-v3-origin/pull/87), to complement the other security procedures already completed or in progress.
+
+The total budget will be $230'000, with a $195'000 fixed prize pool and the rest ($35,000) allocated to the platform and judging fees.
+
+## Motivation
+
+In the middle of December 2024, we shared with the community a proposal for an Aave v3.3 upgrade, focused on adapting the protocol for the upcoming Umbrella system (a new iteration of the Aave Safety Module), together with doing different improvements mainly on the liquidation engine.
+
+The reception by the community has been positive, and since then we have been doing internal reviews and different security procedures. In addition to those, and similar to how we proposed back in [Aave v3.1 with Cantina](https://governance.aave.com/t/arfc-bgd-aave-3-1-cantina-competition/17485), we think due to the nature of this upgrade it can be pretty positive to have an open security contest to maximize the numbers of experts looking for any type of problem in the codebase.
+
+Even if the experience and [outcome](https://github.com/aave-dao/aave-v3-origin/blob/main/audits/02-06-2024-Cantina-contest-AaveV3.1.pdf) with Cantina was pretty positive, part of our security approach is to try different providers, whenever they look solid quality-wise, and/or introduce new mechanics, like in the case of Sherlock.
+
+## Specification
+
+The high-level structure of the contest can be found on the Aave governance forum [HERE](https://governance.aave.com/t/arfc-bgd-aave-v3-3-sherlock-contest/20498#p-51858-specification-3).
+
+This proposal releases the budget required for the contest from the Aave Collector:
+
+- 30'000 USDC to BGD Labs, to refund the part advanced to Sherlock post-ARFC (transaction [HERE](https://etherscan.io/tx/0x396995576313b6578dad47d0ef7ab454b9840c246262bb812a078a092158b058)).
+- 200'000 USDC to Sherlock, to cover the rest of the contest budget.
+
+| **Entity** | **Recipient Address** | **Value** |
+| ---------- | ------------------------------------------ | ------------ |
+| BGD Labs | 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF | 30'000 USDC |
+| Sherlock | 0x666B8EbFbF4D5f0CE56962a25635CfF563F13161 | 200'000 USDC |
+
+## References
+
+- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/29ee27b76f4a86e9f213ed430f4df90547e74c39/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.sol)
+- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/29ee27b76f4a86e9f213ed430f4df90547e74c39/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.t.sol)
+- [Snapshot](https://snapshot.box/#/s:aave.eth/proposal/0x8c04404012d9b74c3e7cebff2ddff3c9d40a280b4cfa7c2fca42be2a59b005ee)
+- [Discussion](https://governance.aave.com/t/arfc-bgd-aave-v3-3-sherlock-contest/20498)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding_20250106.s.sol b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding_20250106.s.sol
new file mode 100644
index 000000000..1fb2d4bb4
--- /dev/null
+++ b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding_20250106.s.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3Ethereum_AaveV33SherlockContestFunding_20250106} from './AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.sol';
+
+/**
+ * @dev Deploy Ethereum
+ * deploy-command: make deploy-ledger contract=src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding_20250106.s.sol:DeployEthereum chain=mainnet
+ * verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/AaveV33SherlockContestFunding_20250106.s.sol/1/run-latest.json
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Ethereum_AaveV33SherlockContestFunding_20250106).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding_20250106.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(
+ type(AaveV3Ethereum_AaveV33SherlockContestFunding_20250106).creationCode
+ );
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV33SherlockContestFunding.md'
+ )
+ );
+ }
+}
diff --git a/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.sol b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.sol
new file mode 100644
index 000000000..955821dfd
--- /dev/null
+++ b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.sol
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol';
+import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {CollectorUtils, ICollector} from 'aave-helpers/src/CollectorUtils.sol';
+/**
+ * @title Aave V3.3 Sherlock Contest Funding
+ * @author BGD Labs @bgdlabs
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x8c04404012d9b74c3e7cebff2ddff3c9d40a280b4cfa7c2fca42be2a59b005ee
+ * - Discussion: https://governance.aave.com/t/arfc-bgd-aave-v3-3-sherlock-contest/20498
+ */
+contract AaveV3Ethereum_AaveV33SherlockContestFunding_20250106 is IProposalGenericExecutor {
+ using CollectorUtils for ICollector;
+
+ address public constant BGD_RECIPIENT = 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF;
+ address public constant SHERLOCK_RECIPIENT = 0x666B8EbFbF4D5f0CE56962a25635CfF563F13161;
+
+ uint256 public constant BGD_USDC_AMOUNT = 30_000e6;
+ uint256 public constant SHERLOCK_USDC_AMOUNT = 200_000e6;
+
+ function execute() external {
+ AaveV3Ethereum.COLLECTOR.withdrawFromV3(
+ CollectorUtils.IOInput({
+ pool: address(AaveV3Ethereum.POOL),
+ underlying: AaveV3EthereumAssets.USDC_UNDERLYING,
+ amount: BGD_USDC_AMOUNT
+ }),
+ BGD_RECIPIENT
+ );
+
+ AaveV3Ethereum.COLLECTOR.withdrawFromV3(
+ CollectorUtils.IOInput({
+ pool: address(AaveV3Ethereum.POOL),
+ underlying: AaveV3EthereumAssets.USDC_UNDERLYING,
+ amount: SHERLOCK_USDC_AMOUNT
+ }),
+ SHERLOCK_RECIPIENT
+ );
+ }
+}
diff --git a/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.t.sol b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.t.sol
new file mode 100644
index 000000000..8810c3de0
--- /dev/null
+++ b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.t.sol
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Ethereum_AaveV33SherlockContestFunding_20250106} from './AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+
+/**
+ * @dev Test for AaveV3Ethereum_AaveV33SherlockContestFunding_20250106
+ * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/AaveV3Ethereum_AaveV33SherlockContestFunding_20250106.t.sol -vv
+ */
+contract AaveV3Ethereum_AaveV33SherlockContestFunding_20250106_Test is ProtocolV3TestBase {
+ AaveV3Ethereum_AaveV33SherlockContestFunding_20250106 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 21566088);
+ proposal = new AaveV3Ethereum_AaveV33SherlockContestFunding_20250106();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3Ethereum_AaveV33SherlockContestFunding_20250106',
+ AaveV3Ethereum.POOL,
+ address(proposal)
+ );
+ }
+
+ function test_consistentBalances() public {
+ uint256 collectorAUSDCBalanceBefore = IERC20(AaveV3EthereumAssets.USDC_A_TOKEN).balanceOf(
+ address(AaveV3Ethereum.COLLECTOR)
+ );
+ uint256 bgdUSDCBalanceBefore = IERC20(AaveV3EthereumAssets.USDC_UNDERLYING).balanceOf(
+ proposal.BGD_RECIPIENT()
+ );
+ uint256 sherlockUSDCBalanceBefore = IERC20(AaveV3EthereumAssets.USDC_UNDERLYING).balanceOf(
+ proposal.SHERLOCK_RECIPIENT()
+ );
+
+ executePayload(vm, address(proposal));
+
+ uint256 collectorAUSDCBalanceAfter = IERC20(AaveV3EthereumAssets.USDC_A_TOKEN).balanceOf(
+ address(AaveV3Ethereum.COLLECTOR)
+ );
+ uint256 bgdUSDCBalanceAfter = IERC20(AaveV3EthereumAssets.USDC_UNDERLYING).balanceOf(
+ proposal.BGD_RECIPIENT()
+ );
+ uint256 sherlockUSDCBalanceAfter = IERC20(AaveV3EthereumAssets.USDC_UNDERLYING).balanceOf(
+ proposal.SHERLOCK_RECIPIENT()
+ );
+
+ uint256 totalAmount = proposal.BGD_USDC_AMOUNT() + proposal.SHERLOCK_USDC_AMOUNT();
+
+ assertApproxEqAbs(collectorAUSDCBalanceAfter, collectorAUSDCBalanceBefore - totalAmount, 1); // due to interest accrued
+
+ assertEq(bgdUSDCBalanceAfter, bgdUSDCBalanceBefore + proposal.BGD_USDC_AMOUNT());
+ assertEq(sherlockUSDCBalanceAfter, sherlockUSDCBalanceBefore + proposal.SHERLOCK_USDC_AMOUNT());
+ }
+}
diff --git a/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/config.ts b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/config.ts
new file mode 100644
index 000000000..04d519c0e
--- /dev/null
+++ b/src/20250106_AaveV3Ethereum_AaveV33SherlockContestFunding/config.ts
@@ -0,0 +1,15 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ author: 'BGD Labs @bgdlabs',
+ pools: ['AaveV3Ethereum'],
+ title: 'Aave v3.3 Sherlock contest funding',
+ shortName: 'AaveV33SherlockContestFunding',
+ date: '20250106',
+ discussion: 'https://governance.aave.com/t/arfc-bgd-aave-v3-3-sherlock-contest/20498',
+ snapshot:
+ 'https://snapshot.box/#/s:aave.eth/proposal/0x8c04404012d9b74c3e7cebff2ddff3c9d40a280b4cfa7c2fca42be2a59b005ee',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {AaveV3Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 21566088}}},
+};
diff --git a/src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation.md b/src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation.md
new file mode 100644
index 000000000..5e950da39
--- /dev/null
+++ b/src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation.md
@@ -0,0 +1,59 @@
+---
+title: "a.DI Celo path activation"
+author: "BGD Labs @bgdlabs"
+discussions: https://governance.aave.com/t/technical-maintenance-proposals/15274/61
+snapshot: Direct-to-AIP
+---
+
+## Simple Summary
+
+Proposal to register the necessary Celo adapters on a.DI, a technical pre-requirement for an activation vote of Aave v3 Celo.
+
+## Motivation
+
+In order to be able to pass messages from Ethereum to Celo via a.DI (Aave Delivery Infrastructure), it is necessary to at least have three valid adapters Ethereum → Celo smart contracts enabled in the system.
+
+The first case of message passing Ethereum → Celo is the activation proposal for an Aave v3 Celo pool and consequently, to be able to execute on the Celo side the payload, the Aave governance should approve in advance the a.DI adapters smart contracts.
+
+This procedure mirrors the requirements on previous networks like Scroll or ZkSync.
+
+## Specification
+
+The proposal payload simply registers pre-deployed Celo adapters (with the necessary configurations to communicate with the Celo a.DI) on the Ethereum a.DI instance.
+
+This is done by calling the enableBridgeAdapters() function on the Ethereum Cross-chain Controller smart contract.
+
+| Network | Hyperlane Adapter | LayerZero Adapter | CCIP Adapter |
+| -------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+| Ethereum | [0x01dcb90Cf13b82Cde4A0BAcC655585a83Af3cCC1](https://etherscan.io/address/0x01dcb90Cf13b82Cde4A0BAcC655585a83Af3cCC1) | [0x8410d9BD353b420ebA8C48ff1B0518426C280FCC](https://etherscan.io/address/0x8410d9BD353b420ebA8C48ff1B0518426C280FCC) | [0x58489B249BfBCF5ef4B30bdE2792086e83122B6f](https://etherscan.io/address/0x58489B249BfBCF5ef4B30bdE2792086e83122B6f) |
+| Celo | [0x7b065E68E70f346B18636Ab86779980287ec73e0](https://celoscan.io/address/0x7b065E68E70f346B18636Ab86779980287ec73e0) | [0x83BC62fbeA15B7Bfe11e8eEE57997afA5451f38C](https://celoscan.io/address/0x83BC62fbeA15B7Bfe11e8eEE57997afA5451f38C) | [0x3d534E8964e7aAcfc702751cc9A2BB6A9fe7d928](https://celoscan.io/address/0x3d534E8964e7aAcfc702751cc9A2BB6A9fe7d928) |
+
+The new a.DI deployments on Linea network are as follows:
+
+| Contract | Address |
+| -------------------------- | -------------------------------------------------------------------------------------------------------------------- |
+| CrossChainController | [0x50F4dAA86F3c747ce15C3C38bD0383200B61d6Dd](https://celoscan.io/address/0x50F4dAA86F3c747ce15C3C38bD0383200B61d6Dd) |
+| Granular Guardian | [0xbE815420A63A413BB8D508d8022C0FF150Ea7C39](https://celoscan.io/address/0xbE815420A63A413BB8D508d8022C0FF150Ea7C39) |
+| Chainlink Emergency Oracle | [0x91b21900E91CD302EBeD05E45D8f270ddAED944d](https://celoscan.io/address/0x91b21900E91CD302EBeD05E45D8f270ddAED944d) |
+
+The new Aave Governance deployments on Linea network are as follows:
+
+| Contract | Address |
+| ------------------- | -------------------------------------------------------------------------------------------------------------------- |
+| PayloadsController | [0xE48E10834C04E394A04BF22a565D063D40b9FA42](https://celoscan.io/address/0xE48E10834C04E394A04BF22a565D063D40b9FA42) |
+| Executor Lvl 1 | [0x1dF462e2712496373A347f8ad10802a5E95f053D](https://celoscan.io/address/0x1dF462e2712496373A347f8ad10802a5E95f053D) |
+| Governance Guardian | [0x056E4C4E80D1D14a637ccbD0412CDAAEc5B51F4E](https://celoscan.io/address/0x056E4C4E80D1D14a637ccbD0412CDAAEc5B51F4E) |
+| BGD Labs Guardian | [0xfD3a6E65e470a7D7D730FFD5D36a9354E8F9F4Ea](https://celoscan.io/address/0xfD3a6E65e470a7D7D730FFD5D36a9354E8F9F4Ea) |
+
+## References
+
+- Adapter Implementations: [HyperLane Bridge Adapters](https://github.com/bgd-labs/aave-delivery-infrastructure/blob/1f1c46af4dd914847849cad4fdd2d26525278821/src/contracts/adapters/hyperLane/HyperLaneAdapter.sol), [LayerZero Bridge Adapters](https://github.com/bgd-labs/aave-delivery-infrastructure/blob/1f1c46af4dd914847849cad4fdd2d26525278821/src/contracts/adapters/layerZero/LayerZeroAdapter.sol), [CCIP Bridge Adapters](https://github.com/bgd-labs/aave-delivery-infrastructure/blob/1f1c46af4dd914847849cad4fdd2d26525278821/src/contracts/adapters/ccip/CCIPAdapter.sol)
+- Payload Implementation: [Payload](https://github.com/bgd-labs/adi-deploy/blob/06785fcb243f179425671691099df927876baeb0/src/adapter_payloads/Ethereum_Celo_Path_Payload.sol)
+- Payload Tests: [tests](https://github.com/bgd-labs/adi-deploy/blob/06785fcb243f179425671691099df927876baeb0/tests/payloads/ethereum/AddCeloPathTest.t.sol)
+- Diffs: [a.DI diffs](https://github.com/bgd-labs/adi-deploy/blob/06785fcb243f179425671691099df927876baeb0/diffs/adi_add_celo_path_to_adiethereum_before_adi_add_celo_path_to_adiethereum_after.md)
+- Snapshot: Direct-to-AIP
+- [Discussion](https://governance.aave.com/t/technical-maintenance-proposals/15274/61)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation_20250109.s.sol b/src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation_20250109.s.sol
new file mode 100644
index 000000000..f743c5df2
--- /dev/null
+++ b/src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation_20250109.s.sol
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+
+address constant PAYLOAD = address(0x8F7E2023686B78E148e65004Ca8AcEf9B2B46922);
+
+/**
+ * @dev Deploy Ethereum
+ * deploy-command: make deploy-ledger contract=src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation_20250109.s.sol:DeployEthereum chain=mainnet
+ * verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/ADICeloPathActivation_20250109.s.sol/1/run-latest.json
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(PAYLOAD);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation_20250109.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(PAYLOAD);
+
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20250109_AaveV3Ethereum_ADICeloPathActivation/ADICeloPathActivation.md'
+ )
+ );
+ }
+}
diff --git a/src/20250109_AaveV3Ethereum_ADICeloPathActivation/config.ts b/src/20250109_AaveV3Ethereum_ADICeloPathActivation/config.ts
new file mode 100644
index 000000000..f6f75824d
--- /dev/null
+++ b/src/20250109_AaveV3Ethereum_ADICeloPathActivation/config.ts
@@ -0,0 +1,14 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ author: 'BGD Labs @bgdlabs',
+ pools: ['AaveV3Ethereum'],
+ title: 'a.DI Celo path activation',
+ shortName: 'ADICeloPathActivation',
+ date: '20250109',
+ discussion: '',
+ snapshot: '',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {AaveV3Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 21586565}}},
+};
diff --git a/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync.md b/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync.md
new file mode 100644
index 000000000..fe3de711b
--- /dev/null
+++ b/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync.md
@@ -0,0 +1,79 @@
+---
+title: "Onboard sUSDe and weETH to Aave v3 on zkSync"
+author: "Aave-chan Initiative"
+discussions: "https://governance.aave.com/t/arfc-onboard-susde-usde-and-weeth-to-aave-v3-on-zksync/19204"
+snapshot: "https://snapshot.box/#/s:aave.eth/proposal/0x6709151a1efa71370a6a0f9a7592d983ed401ac0311cce861fba347081384520"
+---
+
+## Simple Summary
+
+This proposal aims to onboard sUSDe and weETH to the Aave v3 protocol on zkSync. This follows the original plans for further expansion on the network.
+
+## Motivation
+
+The integration of sUSDe, and weETH into Aave v3 on zkSync is following the initial plan for the zkSync network launch. With the successful launch of Aave v3 on zkSync, and some time for monitoring, we believe it is time to start expanding from the initial asset list.
+
+These onboardings also include partnership with Ethena and EtherFi to add ZK token incentives to each market, which will contribute to Aave’s growth on zkSync.
+
+## Specification
+
+The table below illustrates the configured risk parameters for **weETH**
+
+| Parameter | Value |
+| ------------------------- | -----------------------------------------: |
+| Isolation Mode | false |
+| Borrowable | DISABLED |
+| Collateral Enabled | true |
+| Supply Cap (weETH) | 300 |
+| Borrow Cap (weETH) | 1 |
+| Debt Ceiling | USD 0 |
+| LTV | 72.5 % |
+| LT | 75 % |
+| Liquidation Bonus | 7.5 % |
+| Liquidation Protocol Fee | 10 % |
+| Reserve Factor | 45 % |
+| Base Variable Borrow Rate | 0 % |
+| Variable Slope 1 | 7 % |
+| Variable Slope 2 | 300 % |
+| Uoptimal | 30 % |
+| Flashloanable | ENABLED |
+| Siloed Borrowing | DISABLED |
+| Borrowable in Isolation | DISABLED |
+| Oracle | 0x32aF9A0a47B332761c8C90E9eC9f53e46e852b2B |
+
+The table below illustrates the configured risk parameters for **sUSDe**
+
+| Parameter | Value |
+| ------------------------- | -----------------------------------------: |
+| Isolation Mode | true |
+| Borrowable | DISABLED |
+| Collateral Enabled | true |
+| Supply Cap (sUSDe) | 400,000 |
+| Borrow Cap (sUSDe) | 1 |
+| Debt Ceiling | USD 400,000 |
+| LTV | 65 % |
+| LT | 75 % |
+| Liquidation Bonus | 8.5 % |
+| Liquidation Protocol Fee | 10 % |
+| Reserve Factor | 20 % |
+| Base Variable Borrow Rate | 0 % |
+| Variable Slope 1 | 9 % |
+| Variable Slope 2 | 75 % |
+| Uoptimal | 80 % |
+| Flashloanable | ENABLED |
+| Siloed Borrowing | DISABLED |
+| Borrowable in Isolation | DISABLED |
+| Oracle | 0xDaec4cC3a41E423d678428A8Bb29fa1ADF26869a |
+
+Additionally [0x95Cbff6e45C499d45dd8627f3ce179057B5Fbfcc](https://era.zksync.network/address/0x95Cbff6e45C499d45dd8627f3ce179057B5Fbfcc) has been set as the emission admin for weETH, sUSDe and their corresponding aTokens.
+
+## References
+
+- Implementation: [AaveV3ZkSync](https://github.com/bgd-labs/aave-proposals-v3/blob/f2940b91dd47d700d1d8ec8632c262b1409e71a9/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.sol)
+- Tests: [AaveV3ZkSync](https://github.com/bgd-labs/aave-proposals-v3/blob/f2940b91dd47d700d1d8ec8632c262b1409e71a9/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.t.sol)
+- [Snapshot](https://snapshot.box/#/s:aave.eth/proposal/0x6709151a1efa71370a6a0f9a7592d983ed401ac0311cce861fba347081384520)
+- [Discussion](https://governance.aave.com/t/arfc-onboard-susde-usde-and-weeth-to-aave-v3-on-zksync/19204)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol b/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol
new file mode 100644
index 000000000..b10629098
--- /dev/null
+++ b/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils, ChainIds} from 'aave-helpers/src/GovV3Helpers.sol';
+import {AaveV3ZkSync} from 'aave-address-book/AaveV3ZkSync.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {GovernanceV3ZkSync} from 'aave-address-book/GovernanceV3ZkSync.sol';
+import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsZkSync = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsZkSync[0] = GovV3Helpers.buildActionZkSync(
+ vm,
+ 'AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110'
+ );
+ payloads[0] = PayloadsControllerUtils.Payload({
+ chain: ChainIds.ZKSYNC,
+ accessLevel: PayloadsControllerUtils.AccessControl.Level_1,
+ payloadsController: address(GovernanceV3ZkSync.PAYLOADS_CONTROLLER),
+ payloadId: 10
+ });
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync.md'
+ )
+ );
+ }
+}
diff --git a/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/config.ts b/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/config.ts
new file mode 100644
index 000000000..7d5cc73f6
--- /dev/null
+++ b/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/config.ts
@@ -0,0 +1,84 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ configFile: 'src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/config.ts',
+ author: 'Aave-chan Initiative',
+ pools: ['AaveV3ZkSync'],
+ title: 'Onboard sUSDe, USDe and weETH to Aave v3 on zkSync',
+ shortName: 'OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync',
+ date: '20250110',
+ discussion:
+ 'https://governance.aave.com/t/arfc-onboard-susde-usde-and-weeth-to-aave-v3-on-zksync/19204',
+ snapshot:
+ 'https://snapshot.box/#/s:aave.eth/proposal/0x6709151a1efa71370a6a0f9a7592d983ed401ac0311cce861fba347081384520',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {
+ AaveV3ZkSync: {
+ configs: {
+ EMODES_UPDATES: [
+ {
+ eModeCategory: 2,
+ ltv: '90',
+ liqThreshold: '93',
+ liqBonus: '1',
+ label: 'weETH correlated',
+ },
+ ],
+ ASSET_LISTING: [
+ {
+ assetSymbol: 'weETH',
+ decimals: 18,
+ priceFeed: '0x32aF9A0a47B332761c8C90E9eC9f53e46e852b2B',
+ ltv: '72.5',
+ liqThreshold: '75',
+ liqBonus: '7.5',
+ debtCeiling: '0',
+ liqProtocolFee: '10',
+ enabledToBorrow: 'DISABLED',
+ flashloanable: 'ENABLED',
+ borrowableInIsolation: 'DISABLED',
+ withSiloedBorrowing: 'DISABLED',
+ reserveFactor: '45',
+ supplyCap: '300',
+ borrowCap: '1',
+ rateStrategyParams: {
+ optimalUtilizationRate: '30',
+ baseVariableBorrowRate: '0',
+ variableRateSlope1: '7',
+ variableRateSlope2: '300',
+ },
+ asset: '0xc1Fa6E2E8667d9bE0Ca938a54c7E0285E9Df924a',
+ admin: '0x95Cbff6e45C499d45dd8627f3ce179057B5Fbfcc',
+ },
+ {
+ assetSymbol: 'sUSDe',
+ decimals: 18,
+ priceFeed: '0xDaec4cC3a41E423d678428A8Bb29fa1ADF26869a',
+ ltv: '65',
+ liqThreshold: '75',
+ liqBonus: '8.5',
+ debtCeiling: '400000',
+ liqProtocolFee: '10',
+ enabledToBorrow: 'DISABLED',
+ flashloanable: 'ENABLED',
+ borrowableInIsolation: 'DISABLED',
+ withSiloedBorrowing: 'DISABLED',
+ reserveFactor: '20',
+ supplyCap: '400000',
+ borrowCap: '1',
+ rateStrategyParams: {
+ optimalUtilizationRate: '80',
+ baseVariableBorrowRate: '0',
+ variableRateSlope1: '9',
+ variableRateSlope2: '75',
+ },
+ asset: '0xAD17Da2f6Ac76746EF261E835C50b2651ce36DA8',
+ admin: '0x95Cbff6e45C499d45dd8627f3ce179057B5Fbfcc',
+ },
+ ],
+ },
+ cache: {blockNumber: 53365321},
+ },
+ },
+};
diff --git a/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.sol b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.sol
new file mode 100644
index 000000000..484402dcc
--- /dev/null
+++ b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.sol
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol';
+import {AaveV3PayloadEthereum} from 'aave-helpers/src/v3-config-engine/AaveV3PayloadEthereum.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+/**
+ * @title Set REZ, KERNEL and rsETH Emission Admin to ACI
+ * @author Aave-chan Initiative
+ * - Snapshot: Direct-to-AIP
+ * - Discussion: https://governance.aave.com/t/arfc-set-rez-kernel-and-rseth-emission-admin-to-aci/20599
+ */
+contract AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117 is AaveV3PayloadEthereum {
+ address public constant REZ = 0x3B50805453023a91a8bf641e279401a0b23FA6F9;
+ address public constant REZ_ADMIN = 0xdef1FA4CEfe67365ba046a7C630D6B885298E210;
+ address public constant KERNEL = 0x3f80B1c54Ae920Be41a77f8B902259D48cf24cCf;
+ address public constant KERNEL_ADMIN = 0xdef1FA4CEfe67365ba046a7C630D6B885298E210;
+ address public constant rsETH = 0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7;
+ address public constant rsETH_ADMIN = 0xdef1FA4CEfe67365ba046a7C630D6B885298E210;
+
+ function _postExecute() internal override {
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin(REZ, REZ_ADMIN);
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin(KERNEL, KERNEL_ADMIN);
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin(rsETH, rsETH_ADMIN);
+ }
+}
diff --git a/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.t.sol b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.t.sol
new file mode 100644
index 000000000..3fd39ae77
--- /dev/null
+++ b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.t.sol
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers} from 'aave-helpers/src/GovV3Helpers.sol';
+import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117} from './AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.sol';
+
+/**
+ * @dev Test for AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117
+ * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.t.sol -vv
+ */
+contract AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117_Test is ProtocolV3TestBase {
+ AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 21644687);
+ proposal = new AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117',
+ AaveV3Ethereum.POOL,
+ address(proposal)
+ );
+ }
+
+ function test_REZAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ assertEq(
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).getEmissionAdmin(
+ 0x3B50805453023a91a8bf641e279401a0b23FA6F9
+ ),
+ 0xdef1FA4CEfe67365ba046a7C630D6B885298E210
+ );
+ }
+ function test_KERNELAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ assertEq(
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).getEmissionAdmin(
+ 0x3f80B1c54Ae920Be41a77f8B902259D48cf24cCf
+ ),
+ 0xdef1FA4CEfe67365ba046a7C630D6B885298E210
+ );
+ }
+ function test_rsETHAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ assertEq(
+ IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).getEmissionAdmin(
+ 0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7
+ ),
+ 0xdef1FA4CEfe67365ba046a7C630D6B885298E210
+ );
+ }
+}
diff --git a/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI.md b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI.md
new file mode 100644
index 000000000..71ac222be
--- /dev/null
+++ b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI.md
@@ -0,0 +1,32 @@
+---
+title: "Set REZ, KERNEL and rsETH Emission Admin to ACI"
+author: "Aave-chan Initiative"
+discussions: "https://governance.aave.com/t/arfc-set-rez-kernel-and-rseth-emission-admin-to-aci/20599"
+---
+
+## Simple Summary
+
+This proposal enables ACI to distribute REZ, KERNEL and rsETH rewards across several instances of Aave v3.
+
+## Motivation
+
+The Kelp team proposes distributing rsETH and KERNEL rewards on Core and Prime instances of Aave v3 to incentivize rsETH deposits and drive adoption. Similarly, the Renzo team seeks to distribute REZ rewards across Prime instance of Aave v3 to support the growth of ezETH deposits.
+
+Both teams aim to leverage these rewards to foster the continued adoption and expansion of rsETH and ezETH within the Aave ecosystem. The ACI team will manage the distribution of rewards on behalf of Kelp and Renzo.
+
+## Specification
+
+### Aave Core & Prime:
+
+The emission admin role for [KERNEL](https://etherscan.io/address/0x3f80B1c54Ae920Be41a77f8B902259D48cf24cCf), [REZ](https://etherscan.io/address/0x3B50805453023a91a8bf641e279401a0b23FA6F9) and [rsETH](https://etherscan.io/address/0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7) is granted to [0xdef1FA4CEfe67365ba046a7C630D6B885298E210](https://etherscan.io/address/0xdef1FA4CEfe67365ba046a7C630D6B885298E210).
+
+## References
+
+- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/7c4fd17673768bf1748ffbdfd740f10d2c038176/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.sol)
+- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/7c4fd17673768bf1748ffbdfd740f10d2c038176/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.t.sol)
+- Snapshot: Direct-to-AIP
+- [Discussion](https://governance.aave.com/t/arfc-set-rez-kernel-and-rseth-emission-admin-to-aci/20599)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI_20250117.s.sol b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI_20250117.s.sol
new file mode 100644
index 000000000..7bc02635e
--- /dev/null
+++ b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI_20250117.s.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117} from './AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117.sol';
+
+/**
+ * @dev Deploy Ethereum
+ * deploy-command: make deploy-ledger contract=src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI_20250117.s.sol:DeployEthereum chain=mainnet
+ * verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/SetREZKERNELAndRsETHEmissionAdminToACI_20250117.s.sol/1/run-latest.json
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI_20250117.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(
+ type(AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI_20250117).creationCode
+ );
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/SetREZKERNELAndRsETHEmissionAdminToACI.md'
+ )
+ );
+ }
+}
diff --git a/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/config.ts b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/config.ts
new file mode 100644
index 000000000..bcf739644
--- /dev/null
+++ b/src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/config.ts
@@ -0,0 +1,40 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ configFile: 'src/20250117_AaveV3Ethereum_SetREZKERNELAndRsETHEmissionAdminToACI/config.ts',
+ force: true,
+ author: 'Aave-chan Initiative',
+ title: 'Set REZ, KERNEL and rsETH Emission Admin to ACI',
+ discussion:
+ 'https://governance.aave.com/t/arfc-set-rez-kernel-and-rseth-emission-admin-to-aci/20599',
+ snapshot: 'Direct-to-AIP',
+ pools: ['AaveV3Ethereum'],
+ shortName: 'SetREZKERNELAndRsETHEmissionAdminToACI',
+ date: '20250117',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {
+ AaveV3Ethereum: {
+ configs: {
+ EMISSION: [
+ {
+ asset: '0x3B50805453023a91a8bf641e279401a0b23FA6F9',
+ symbol: 'REZ',
+ admin: '0xdef1FA4CEfe67365ba046a7C630D6B885298E210',
+ },
+ {
+ asset: '0x3f80b1c54ae920be41a77f8b902259d48cf24ccf',
+ symbol: 'KERNEL',
+ admin: '0xdef1FA4CEfe67365ba046a7C630D6B885298E210',
+ },
+ {
+ asset: '0xa1290d69c65a6fe4df752f95823fae25cb99e5a7',
+ symbol: 'rsETH',
+ admin: '0xdef1FA4CEfe67365ba046a7C630D6B885298E210',
+ },
+ ],
+ },
+ cache: {blockNumber: 21644687},
+ },
+ },
+};
diff --git a/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.sol b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.sol
new file mode 100644
index 000000000..7e4e299e1
--- /dev/null
+++ b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.sol
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3EthereumLidoAssets} from 'aave-address-book/AaveV3EthereumLido.sol';
+import {AaveV3PayloadEthereumLido} from 'aave-helpers/src/v3-config-engine/AaveV3PayloadEthereumLido.sol';
+import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol';
+import {IAaveV3ConfigEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol';
+/**
+ * @title Update Lido GHO base borrow rate
+ * @author ACI
+ * - Snapshot: Direct-to-AIP
+ * - Discussion: https://governance.aave.com/t/arfc-risk-stewards-reduce-gho-borrow-rate-prime-instance/20501
+ */
+contract AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121 is AaveV3PayloadEthereumLido {
+ function rateStrategiesUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.RateStrategyUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.RateStrategyUpdate[]
+ memory rateStrategies = new IAaveV3ConfigEngine.RateStrategyUpdate[](1);
+ rateStrategies[0] = IAaveV3ConfigEngine.RateStrategyUpdate({
+ asset: AaveV3EthereumLidoAssets.GHO_UNDERLYING,
+ params: IAaveV3ConfigEngine.InterestRateInputData({
+ optimalUsageRatio: EngineFlags.KEEP_CURRENT,
+ baseVariableBorrowRate: 8_50,
+ variableRateSlope1: EngineFlags.KEEP_CURRENT,
+ variableRateSlope2: EngineFlags.KEEP_CURRENT
+ })
+ });
+
+ return rateStrategies;
+ }
+}
diff --git a/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.t.sol b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.t.sol
new file mode 100644
index 000000000..cc5732eba
--- /dev/null
+++ b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.t.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3EthereumLido} from 'aave-address-book/AaveV3EthereumLido.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
+import {AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121} from './AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.sol';
+
+/**
+ * @dev Test for AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121
+ * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.t.sol -vv
+ */
+contract AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121_Test is ProtocolV3TestBase {
+ AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 21673369);
+ proposal = new AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121',
+ AaveV3EthereumLido.POOL,
+ address(proposal)
+ );
+ }
+}
diff --git a/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate.md b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate.md
new file mode 100644
index 000000000..dc634444c
--- /dev/null
+++ b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate.md
@@ -0,0 +1,41 @@
+---
+title: "Update Lido GHO base borrow rate"
+author: "ACI"
+discussions: "https://governance.aave.com/t/arfc-risk-stewards-reduce-gho-borrow-rate-prime-instance/20501"
+snapshot: Direct-to-AIP
+---
+
+## Simple Summary
+
+This publication proposes reducing the base borrow rate Parameter for the GHO Reserve on Prime instance by 2.00%.
+
+## Motivation
+
+At launch, the GHO Borrow Rate was configured to suit the more buoyant market conditions of the time. Since then, market conditions have cooled and to date, the GHO reserve on Prime has experienced limited borrowing activity. Utilization is 2.30% with less than 120k GHO borrowed.
+
+The Borrow Rate for USDS on Prime is trending slightly above 10% which is slightly lower than USDT on Core instance after recent deposits has reduced utilization.
+
+Staked USDe is currently at capacity on Prime and is shown to be generating 12.50% on the Ethena dashboard. When the sUSDe supply cap is lifted, we expect demand for GHO debt to emerge, and if not initially, then when perpetual funding rates improve.
+
+Additionally, the GHO Stewards lowered the Borrow Rate on the Core instance to 12.50% which is 1% less than the GHO Borrow Rate on Prime at the Uoptimal. Amending the GHO Borrow Rate at the Uoptimal on Prime to be less than the GHO Borrow Rate on Core encourages users to borrow GHO from Prime.
+
+When demand for GHO on Prime emerges, providing the peg permits, the Borrow Cap shall be increased improving the overall efficiency of the GHO reserve, resulting in a higher deposit rate.
+
+## Specification
+
+The GHO base borrow rate Parameter on Prime instance of Aave v3 is to be revised as follows:
+
+| Description | Current | Proposed | Change |
+| ----------- | ------- | -------- | ------ |
+| Borrow Rate | 10.50% | 8.50% | -2.00% |
+
+## References
+
+- Implementation: [AaveV3EthereumLido](https://github.com/bgd-labs/aave-proposals-v3/blob/051dd6743951609879d6358022e6521528046b7d/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.sol)
+- Tests: [AaveV3EthereumLido](https://github.com/bgd-labs/aave-proposals-v3/blob/051dd6743951609879d6358022e6521528046b7d/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.t.sol)
+- Snapshot: Direct-to-AIP
+- [Discussion](https://governance.aave.com/t/arfc-risk-stewards-reduce-gho-borrow-rate-prime-instance/20501)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate_20250121.s.sol b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate_20250121.s.sol
new file mode 100644
index 000000000..85b66241c
--- /dev/null
+++ b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate_20250121.s.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
+import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
+import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121} from './AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121.sol';
+
+/**
+ * @dev Deploy Ethereum
+ * deploy-command: make deploy-ledger contract=src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate_20250121.s.sol:DeployEthereum chain=mainnet
+ * verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/UpdateLidoGHOBaseBorrowRate_20250121.s.sol/1/run-latest.json
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = GovV3Helpers.deployDeterministic(
+ type(AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121).creationCode
+ );
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate_20250121.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(
+ type(AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate_20250121).creationCode
+ );
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal(
+ vm,
+ payloads,
+ GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
+ GovV3Helpers.ipfsHashFile(
+ vm,
+ 'src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/UpdateLidoGHOBaseBorrowRate.md'
+ )
+ );
+ }
+}
diff --git a/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/config.ts b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/config.ts
new file mode 100644
index 000000000..254026d8e
--- /dev/null
+++ b/src/20250121_AaveV3EthereumLido_UpdateLidoGHOBaseBorrowRate/config.ts
@@ -0,0 +1,32 @@
+import {ConfigFile} from '../../generator/types';
+export const config: ConfigFile = {
+ rootOptions: {
+ pools: ['AaveV3EthereumLido'],
+ title: 'Update Lido GHO base borrow rate',
+ shortName: 'UpdateLidoGHOBaseBorrowRate',
+ date: '20250121',
+ author: 'ACI',
+ discussion:
+ 'https://governance.aave.com/t/arfc-risk-stewards-reduce-gho-borrow-rate-prime-instance/20501',
+ snapshot: '',
+ votingNetwork: 'POLYGON',
+ },
+ poolOptions: {
+ AaveV3EthereumLido: {
+ configs: {
+ RATE_UPDATE_V3: [
+ {
+ asset: 'GHO',
+ params: {
+ optimalUtilizationRate: '',
+ baseVariableBorrowRate: '8.5',
+ variableRateSlope1: '',
+ variableRateSlope2: '',
+ },
+ },
+ ],
+ },
+ cache: {blockNumber: 21673369},
+ },
+ },
+};
diff --git a/src/interfaces/IGhoOracle.sol b/src/interfaces/IGhoOracle.sol
new file mode 100644
index 000000000..65d895d9b
--- /dev/null
+++ b/src/interfaces/IGhoOracle.sol
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: AGPL-3.0
+pragma solidity ^0.8.0;
+
+/**
+ * @title IGhoOracle
+ * @notice Price feed for GHO (USD denominated)
+ * @dev Price fixed at 1 USD, Chainlink format with 8 decimals
+ * @author Aave
+ */
+interface IGhoOracle {
+ /**
+ * @notice Returns the price of a unit of GHO (USD denominated)
+ * @dev GHO price is fixed at 1 USD
+ * @return The price of a unit of GHO (with 8 decimals)
+ */
+ function latestAnswer() external view returns (int256);
+
+ /**
+ * @notice Returns the number of decimals the price is formatted with
+ * @return The number of decimals
+ */
+ function decimals() external view returns (uint8);
+}
diff --git a/src/interfaces/IGhoToken.sol b/src/interfaces/IGhoToken.sol
index d37fb1643..db432b820 100644
--- a/src/interfaces/IGhoToken.sol
+++ b/src/interfaces/IGhoToken.sol
@@ -10,6 +10,8 @@ interface IGhoToken is IERC20 {
string label;
}
+ function initialize(address admin) external;
+
/**
* @notice Mints the requested amount of tokens to the account address.
* @dev Only facilitators with enough bucket capacity available can mint.
@@ -76,6 +78,12 @@ interface IGhoToken is IERC20 {
*/
function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256);
+ /**
+ * @notice Returns the identifier of the Facilitator Manager Role
+ * @return The bytes32 id hash of the FacilitatorManager role
+ */
+ function FACILITATOR_MANAGER_ROLE() external pure returns (bytes32);
+
/**
* @notice Returns the identifier of the Bucket Manager Role
* @return The bytes32 id hash of the BucketManager role
diff --git a/src/interfaces/ccip/IEVM2EVMOffRamp.sol b/src/interfaces/ccip/IEVM2EVMOffRamp.sol
index 3c79cb4ef..34f565594 100644
--- a/src/interfaces/ccip/IEVM2EVMOffRamp.sol
+++ b/src/interfaces/ccip/IEVM2EVMOffRamp.sol
@@ -20,18 +20,18 @@ interface IEVM2EVMOffRamp_1_2 is ITypeAndVersion {
}
interface IEVM2EVMOffRamp_1_5 is ITypeAndVersion {
- /// @notice Execute a single message.
- /// @param message The message that will be executed.
- /// @param offchainTokenData Token transfer data to be passed to TokenPool.
- /// @dev We make this external and callable by the contract itself, in order to try/catch
- /// its execution and enforce atomicity among successful message processing and token transfer.
- /// @dev We use ERC-165 to check for the ccipReceive interface to permit sending tokens to contracts
- /// (for example smart contract wallets) without an associated message.
- function executeSingleMessage(
- IInternal.EVM2EVMMessage calldata message,
- bytes[] calldata offchainTokenData,
- uint32[] memory tokenGasOverrides
- ) external;
+ /// @notice Static offRamp config
+ /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
+ //solhint-disable gas-struct-packing
+ struct StaticConfig {
+ address commitStore; // ────────╮ CommitStore address on the destination chain
+ uint64 chainSelector; // ───────╯ Destination chainSelector
+ uint64 sourceChainSelector; // ─╮ Source chainSelector
+ address onRamp; // ─────────────╯ OnRamp address on the source chain
+ address prevOffRamp; // Address of previous-version OffRamp
+ address rmnProxy; // RMN proxy address
+ address tokenAdminRegistry; // Token admin registry address
+ }
/// @notice Dynamic offRamp config
/// @dev since OffRampConfig is part of OffRampConfigChanged event, if changing it, we should update the ABI on Atlas
@@ -43,7 +43,25 @@ interface IEVM2EVMOffRamp_1_5 is ITypeAndVersion {
address priceRegistry; // Price registry address
}
+ /// @notice Returns the static config.
+ /// @dev This function will always return the same struct as the contents is static and can never change.
+ /// RMN depends on this function, if changing, please notify the RMN maintainers.
+ function getStaticConfig() external view returns (StaticConfig memory);
+
/// @notice Returns the current dynamic config.
/// @return The current config.
function getDynamicConfig() external view returns (DynamicConfig memory);
+
+ /// @notice Execute a single message.
+ /// @param message The message that will be executed.
+ /// @param offchainTokenData Token transfer data to be passed to TokenPool.
+ /// @dev We make this external and callable by the contract itself, in order to try/catch
+ /// its execution and enforce atomicity among successful message processing and token transfer.
+ /// @dev We use ERC-165 to check for the ccipReceive interface to permit sending tokens to contracts
+ /// (for example smart contract wallets) without an associated message.
+ function executeSingleMessage(
+ IInternal.EVM2EVMMessage calldata message,
+ bytes[] calldata offchainTokenData,
+ uint32[] memory tokenGasOverrides
+ ) external;
}
diff --git a/src/interfaces/ccip/IEVM2EVMOnRamp.sol b/src/interfaces/ccip/IEVM2EVMOnRamp.sol
index 306f1ebf0..f4521ff76 100644
--- a/src/interfaces/ccip/IEVM2EVMOnRamp.sol
+++ b/src/interfaces/ccip/IEVM2EVMOnRamp.sol
@@ -17,6 +17,17 @@ interface IEVM2EVMOnRamp is ITypeAndVersion {
bool isEnabled; // ─────────────────╯ Whether this token has custom transfer fees
}
+ struct StaticConfig {
+ address linkToken; // ────────╮ Link token address
+ uint64 chainSelector; // ─────╯ Source chainSelector
+ uint64 destChainSelector; // ─╮ Destination chainSelector
+ uint64 defaultTxGasLimit; // │ Default gas limit for a tx
+ uint96 maxNopFeesJuels; // ───╯ Max nop fee balance onramp can have
+ address prevOnRamp; // Address of previous-version OnRamp
+ address rmnProxy; // Address of RMN proxy
+ address tokenAdminRegistry; // Address of the token admin registry
+ }
+
struct DynamicConfig {
address router; // ──────────────────────────╮ Router address
uint16 maxNumberOfTokensPerMsg; // │ Maximum number of distinct ERC20 token transferred per message
@@ -35,17 +46,6 @@ interface IEVM2EVMOnRamp is ITypeAndVersion {
bool enforceOutOfOrder; // ──────────────────╯ Whether to enforce the allowOutOfOrderExecution extraArg value to be true.
}
- struct StaticConfig {
- address linkToken; // ────────╮ Link token address
- uint64 chainSelector; // ─────╯ Source chainSelector
- uint64 destChainSelector; // ─╮ Destination chainSelector
- uint64 defaultTxGasLimit; // │ Default gas limit for a tx
- uint96 maxNopFeesJuels; // ───╯ Max nop fee balance onramp can have
- address prevOnRamp; // Address of previous-version OnRamp
- address rmnProxy; // Address of RMN proxy
- address tokenAdminRegistry; // Address of the token admin registry
- }
-
/// @notice Gets the next sequence number to be used in the onRamp
/// @return the next sequence number to be used
function getExpectedNextSequenceNumber() external view returns (uint64);
diff --git a/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.sol b/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.sol
new file mode 100644
index 000000000..cba6bbe8f
--- /dev/null
+++ b/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.sol
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3ZkSync, AaveV3ZkSyncAssets} from 'aave-address-book/AaveV3ZkSync.sol';
+import {AaveV3PayloadZkSync} from 'aave-helpers/src/v3-config-engine/AaveV3PayloadZkSync.sol';
+import {EngineFlags} from 'aave-v3-origin/contracts/extensions/v3-config-engine/EngineFlags.sol';
+import {IAaveV3ConfigEngine} from 'aave-v3-origin/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+/**
+ * @title Onboard sUSDe, USDe and weETH to Aave v3 on zkSync
+ * @author Aave-chan Initiative
+ * - Snapshot: https://snapshot.box/#/s:aave.eth/proposal/0x6709151a1efa71370a6a0f9a7592d983ed401ac0311cce861fba347081384520
+ * - Discussion: https://governance.aave.com/t/arfc-onboard-susde-usde-and-weeth-to-aave-v3-on-zksync/19204
+ */
+contract AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110 is AaveV3PayloadZkSync {
+ using SafeERC20 for IERC20;
+
+ address public constant weETH = 0xc1Fa6E2E8667d9bE0Ca938a54c7E0285E9Df924a;
+ uint256 public constant weETH_SEED_AMOUNT = 25e15;
+ address public constant weETH_LM_ADMIN = 0x95Cbff6e45C499d45dd8627f3ce179057B5Fbfcc;
+
+ address public constant sUSDe = 0xAD17Da2f6Ac76746EF261E835C50b2651ce36DA8;
+ uint256 public constant sUSDe_SEED_AMOUNT = 100e18;
+ address public constant sUSDe_LM_ADMIN = 0x95Cbff6e45C499d45dd8627f3ce179057B5Fbfcc;
+
+ function _postExecute() internal override {
+ IERC20(weETH).forceApprove(address(AaveV3ZkSync.POOL), weETH_SEED_AMOUNT);
+ AaveV3ZkSync.POOL.supply(weETH, weETH_SEED_AMOUNT, address(AaveV3ZkSync.COLLECTOR), 0);
+
+ (address aweETH, , ) = AaveV3ZkSync.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ weETH
+ );
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).setEmissionAdmin(weETH, weETH_LM_ADMIN);
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).setEmissionAdmin(aweETH, weETH_LM_ADMIN);
+
+ IERC20(sUSDe).forceApprove(address(AaveV3ZkSync.POOL), sUSDe_SEED_AMOUNT);
+ AaveV3ZkSync.POOL.supply(sUSDe, sUSDe_SEED_AMOUNT, address(AaveV3ZkSync.COLLECTOR), 0);
+
+ (address asUSDe, , ) = AaveV3ZkSync.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ sUSDe
+ );
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).setEmissionAdmin(sUSDe, sUSDe_LM_ADMIN);
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).setEmissionAdmin(asUSDe, sUSDe_LM_ADMIN);
+ }
+
+ function eModeCategoriesUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.EModeCategoryUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.EModeCategoryUpdate[]
+ memory eModeUpdates = new IAaveV3ConfigEngine.EModeCategoryUpdate[](1);
+
+ eModeUpdates[0] = IAaveV3ConfigEngine.EModeCategoryUpdate({
+ eModeCategory: 2,
+ ltv: 90_00,
+ liqThreshold: 93_00,
+ liqBonus: 1_00,
+ label: 'weETH correlated'
+ });
+
+ return eModeUpdates;
+ }
+ function assetsEModeUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.AssetEModeUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.AssetEModeUpdate[]
+ memory assetEModeUpdates = new IAaveV3ConfigEngine.AssetEModeUpdate[](2);
+
+ assetEModeUpdates[0] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: weETH,
+ eModeCategory: 2,
+ borrowable: EngineFlags.DISABLED,
+ collateral: EngineFlags.ENABLED
+ });
+
+ assetEModeUpdates[1] = IAaveV3ConfigEngine.AssetEModeUpdate({
+ asset: AaveV3ZkSyncAssets.WETH_UNDERLYING,
+ eModeCategory: 2,
+ borrowable: EngineFlags.ENABLED,
+ collateral: EngineFlags.DISABLED
+ });
+
+ return assetEModeUpdates;
+ }
+ function newListings() public pure override returns (IAaveV3ConfigEngine.Listing[] memory) {
+ IAaveV3ConfigEngine.Listing[] memory listings = new IAaveV3ConfigEngine.Listing[](2);
+
+ listings[0] = IAaveV3ConfigEngine.Listing({
+ asset: weETH,
+ assetSymbol: 'weETH',
+ priceFeed: 0x32aF9A0a47B332761c8C90E9eC9f53e46e852b2B,
+ enabledToBorrow: EngineFlags.DISABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
+ ltv: 72_50,
+ liqThreshold: 75_00,
+ liqBonus: 7_50,
+ reserveFactor: 45_00,
+ supplyCap: 300,
+ borrowCap: 1,
+ debtCeiling: 0,
+ liqProtocolFee: 10_00,
+ rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({
+ optimalUsageRatio: 30_00,
+ baseVariableBorrowRate: 0,
+ variableRateSlope1: 7_00,
+ variableRateSlope2: 300_00
+ })
+ });
+ listings[1] = IAaveV3ConfigEngine.Listing({
+ asset: sUSDe,
+ assetSymbol: 'sUSDe',
+ priceFeed: 0xDaec4cC3a41E423d678428A8Bb29fa1ADF26869a,
+ enabledToBorrow: EngineFlags.DISABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
+ ltv: 65_00,
+ liqThreshold: 75_00,
+ liqBonus: 8_50,
+ reserveFactor: 20_00,
+ supplyCap: 400_000,
+ borrowCap: 1,
+ debtCeiling: 400_000,
+ liqProtocolFee: 10_00,
+ rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({
+ optimalUsageRatio: 80_00,
+ baseVariableBorrowRate: 0,
+ variableRateSlope1: 9_00,
+ variableRateSlope2: 75_00
+ })
+ });
+
+ return listings;
+ }
+}
diff --git a/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.t.sol b/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.t.sol
new file mode 100644
index 000000000..b5645963b
--- /dev/null
+++ b/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.t.sol
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers} from 'aave-helpers/src/GovV3Helpers.sol';
+import {AaveV3ZkSync} from 'aave-address-book/AaveV3ZkSync.sol';
+import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
+import {IEmissionManager} from 'aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/zksync/src/ProtocolV3TestBase.sol';
+import {AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110} from './AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.sol';
+
+/**
+ * @dev Test for AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110
+ * command: FOUNDRY_PROFILE=zksync forge test --zksync --match-path=zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.t.sol -vv
+ */
+contract AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110_Test is ProtocolV3TestBase {
+ AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110 internal proposal;
+
+ function setUp() public override {
+ vm.createSelectFork(vm.rpcUrl('zksync'), 53365321);
+ proposal = new AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110();
+
+ super.setUp();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest(
+ 'AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110',
+ AaveV3ZkSync.POOL,
+ address(proposal)
+ );
+ }
+
+ function test_collectorHasweETHFunds() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aTokenAddress, , ) = AaveV3ZkSync
+ .AAVE_PROTOCOL_DATA_PROVIDER
+ .getReserveTokensAddresses(proposal.weETH());
+ assertGe(IERC20(aTokenAddress).balanceOf(address(AaveV3ZkSync.COLLECTOR)), 25 * 10 ** 15);
+ }
+
+ function test_weETHAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aweETH, , ) = AaveV3ZkSync.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.weETH()
+ );
+ assertEq(
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).getEmissionAdmin(proposal.weETH()),
+ proposal.weETH_LM_ADMIN()
+ );
+ assertEq(
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).getEmissionAdmin(aweETH),
+ proposal.weETH_LM_ADMIN()
+ );
+ }
+
+ function test_collectorHassUSDeFunds() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address aTokenAddress, , ) = AaveV3ZkSync
+ .AAVE_PROTOCOL_DATA_PROVIDER
+ .getReserveTokensAddresses(proposal.sUSDe());
+ assertGe(IERC20(aTokenAddress).balanceOf(address(AaveV3ZkSync.COLLECTOR)), 100 * 10 ** 18);
+ }
+
+ function test_sUSDeAdmin() public {
+ GovV3Helpers.executePayload(vm, address(proposal));
+ (address asUSDe, , ) = AaveV3ZkSync.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
+ proposal.sUSDe()
+ );
+ assertEq(
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).getEmissionAdmin(proposal.sUSDe()),
+ proposal.sUSDe_LM_ADMIN()
+ );
+ assertEq(
+ IEmissionManager(AaveV3ZkSync.EMISSION_MANAGER).getEmissionAdmin(asUSDe),
+ proposal.sUSDe_LM_ADMIN()
+ );
+ }
+}
diff --git a/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol b/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol
new file mode 100644
index 000000000..bd454140c
--- /dev/null
+++ b/zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore} from 'aave-helpers/src/GovV3Helpers.sol';
+import {ZkSyncScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
+import {AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110} from './AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.sol';
+
+// @dev wrapper factory contract for deploying the payload
+contract Deploy_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110 {
+ address public immutable PAYLOAD;
+
+ constructor() {
+ PAYLOAD = GovV3Helpers.deployDeterministicZkSync(
+ type(AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110).creationCode
+ );
+ }
+}
+
+/**
+ * @dev Deploy ZkSync
+ * deploy-command: make deploy-pk FOUNDRY_PROFILE=zksync contract=zksync/src/20250110_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync/OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110.s.sol:DeployZkSync chain=zksync
+ */
+contract DeployZkSync is ZkSyncScript {
+ function run() external broadcast {
+ // deploy payloads
+ address payload0 = new Deploy_AaveV3ZkSync_OnboardSUSDeUSDeAndWeETHToAaveV3OnZkSync_20250110()
+ .PAYLOAD();
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(payload0);
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}