|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity ^0.8.18; |
| 3 | + |
| 4 | +import 'forge-std/Script.sol'; |
| 5 | +import 'forge-std/console.sol'; |
| 6 | + |
| 7 | +import {AaveV3BatchOrchestration} from '../src/deployments/projects/aave-v3-batched/AaveV3BatchOrchestration.sol'; |
| 8 | +import {IAaveV3ConfigEngine} from '../src/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol'; |
| 9 | +import {EngineFlags} from '../src/contracts/extensions/v3-config-engine/EngineFlags.sol'; |
| 10 | +import {MockAggregatorSetPrice} from '../tests/invariants/utils/mocks/MockAggregatorSetPrice.sol'; |
| 11 | +import {SequencerOracle} from '../src/contracts/mocks/oracle/SequencerOracle.sol'; |
| 12 | +import {TestnetERC20} from '../src/contracts/mocks/testnet-helpers/TestnetERC20.sol'; |
| 13 | +import {IERC20} from '../src/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; |
| 14 | +import {IPool} from '../src/contracts/interfaces/IPool.sol'; |
| 15 | +import {IACLManager} from '../src/contracts/interfaces/IACLManager.sol'; |
| 16 | +import {MarketReport, MarketConfig, DeployFlags, Roles} from '../src/deployments/interfaces/IMarketReportTypes.sol'; |
| 17 | + |
| 18 | +struct DeployedAssets { |
| 19 | + TestnetERC20 usdc; |
| 20 | + TestnetERC20 usdt; |
| 21 | + TestnetERC20 wbtc; |
| 22 | + TestnetERC20 weth; |
| 23 | + MockAggregatorSetPrice usdcOracle; |
| 24 | + MockAggregatorSetPrice usdtOracle; |
| 25 | + MockAggregatorSetPrice wbtcOracle; |
| 26 | + MockAggregatorSetPrice wethOracle; |
| 27 | + SequencerOracle sequencerOracle; |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * @notice One-shot script to deploy a full Aave v3 market on Base Sepolia, together with |
| 32 | + * 4 mock assets and adjustable price feeds. |
| 33 | + * @dev Transactions are broadcasted; set PRIVATE_KEY env. RPC handled by forge args. |
| 34 | + */ |
| 35 | +contract DeployAaveV3BaseSepolia is Script { |
| 36 | + function run() external { |
| 37 | + uint256 deployerPk = _getPrivateKey(); |
| 38 | + address deployer = vm.addr(deployerPk); |
| 39 | + |
| 40 | + vm.startBroadcast(deployerPk); |
| 41 | + |
| 42 | + // 1) Deploy test tokens |
| 43 | + DeployedAssets memory a; |
| 44 | + a.usdc = new TestnetERC20('USDC', 'USDC', 6, deployer); |
| 45 | + a.usdt = new TestnetERC20('USDT', 'USDT', 6, deployer); |
| 46 | + a.wbtc = new TestnetERC20('WBTC', 'WBTC', 8, deployer); |
| 47 | + a.weth = new TestnetERC20('WETH', 'WETH', 18, deployer); |
| 48 | + |
| 49 | + // Mint 1e9 units (before decimals) to deployer |
| 50 | + uint256 baseSupply = 1e9; |
| 51 | + a.usdc.mint(deployer, baseSupply * 10 ** a.usdc.decimals()); |
| 52 | + a.usdt.mint(deployer, baseSupply * 10 ** a.usdt.decimals()); |
| 53 | + a.wbtc.mint(deployer, baseSupply * 10 ** a.wbtc.decimals()); |
| 54 | + a.weth.mint(deployer, baseSupply * 10 ** a.weth.decimals()); |
| 55 | + |
| 56 | + // 2) Deploy adjustable price feeds (8 decimals) |
| 57 | + a.usdcOracle = new MockAggregatorSetPrice(1e8); // $1 |
| 58 | + a.usdtOracle = new MockAggregatorSetPrice(1e8); // $1 |
| 59 | + a.wbtcOracle = new MockAggregatorSetPrice(88700e8); // $88,700 |
| 60 | + a.wethOracle = new MockAggregatorSetPrice(3078e8); // $3,078 |
| 61 | + |
| 62 | + // 3) Deploy sequencer uptime oracle (for sentinel) and mark as healthy |
| 63 | + a.sequencerOracle = new SequencerOracle(deployer); |
| 64 | + a.sequencerOracle.setAnswer(false, block.timestamp); |
| 65 | + |
| 66 | + // 4) Build config + deploy Aave V3 (L2Pool) |
| 67 | + Roles memory roles; |
| 68 | + roles.marketOwner = deployer; |
| 69 | + roles.poolAdmin = deployer; |
| 70 | + roles.emergencyAdmin = deployer; |
| 71 | + |
| 72 | + MarketConfig memory config; |
| 73 | + config.marketId = 'Aave V3 Base Sepolia'; |
| 74 | + config.providerId = 84532; |
| 75 | + config.oracleDecimals = 8; |
| 76 | + config.flashLoanPremium = 0.0005e4; // 0.05% |
| 77 | + config.networkBaseTokenPriceInUsdProxyAggregator = address(a.wethOracle); |
| 78 | + config.marketReferenceCurrencyPriceInUsdProxyAggregator = address(a.usdcOracle); |
| 79 | + config.l2SequencerUptimeFeed = address(a.sequencerOracle); |
| 80 | + config.l2PriceOracleSentinelGracePeriod = 1 hours; |
| 81 | + config.wrappedNativeToken = address(a.weth); // treated as plain ERC20 |
| 82 | + |
| 83 | + DeployFlags memory flags; |
| 84 | + flags.l2 = true; |
| 85 | + |
| 86 | + MarketReport memory report; |
| 87 | + report = AaveV3BatchOrchestration.deployAaveV3(deployer, roles, config, flags, report); |
| 88 | + |
| 89 | + // 5) List assets via ConfigEngine with custom implementations |
| 90 | + IAaveV3ConfigEngine configEngine = IAaveV3ConfigEngine(report.configEngine); |
| 91 | + IAaveV3ConfigEngine.PoolContext memory context = IAaveV3ConfigEngine.PoolContext({ |
| 92 | + networkName: 'Base Sepolia', |
| 93 | + networkAbbreviation: 'BSP' |
| 94 | + }); |
| 95 | + |
| 96 | + IAaveV3ConfigEngine.ListingWithCustomImpl[] memory listings = new IAaveV3ConfigEngine |
| 97 | + .ListingWithCustomImpl[](4); |
| 98 | + |
| 99 | + IAaveV3ConfigEngine.InterestRateInputData memory rateParams = IAaveV3ConfigEngine |
| 100 | + .InterestRateInputData({ |
| 101 | + optimalUsageRatio: 45_00, |
| 102 | + baseVariableBorrowRate: 0, |
| 103 | + variableRateSlope1: 4_00, |
| 104 | + variableRateSlope2: 60_00 |
| 105 | + }); |
| 106 | + |
| 107 | + listings[0] = _buildListing( |
| 108 | + address(a.usdc), |
| 109 | + 'USDC', |
| 110 | + address(a.usdcOracle), |
| 111 | + rateParams, |
| 112 | + report.aToken, |
| 113 | + report.variableDebtToken |
| 114 | + ); |
| 115 | + listings[1] = _buildListing( |
| 116 | + address(a.usdt), |
| 117 | + 'USDT', |
| 118 | + address(a.usdtOracle), |
| 119 | + rateParams, |
| 120 | + report.aToken, |
| 121 | + report.variableDebtToken |
| 122 | + ); |
| 123 | + listings[2] = _buildListing( |
| 124 | + address(a.wbtc), |
| 125 | + 'WBTC', |
| 126 | + address(a.wbtcOracle), |
| 127 | + rateParams, |
| 128 | + report.aToken, |
| 129 | + report.variableDebtToken |
| 130 | + ); |
| 131 | + listings[3] = _buildListing( |
| 132 | + address(a.weth), |
| 133 | + 'WETH', |
| 134 | + address(a.wethOracle), |
| 135 | + rateParams, |
| 136 | + report.aToken, |
| 137 | + report.variableDebtToken |
| 138 | + ); |
| 139 | + |
| 140 | + IACLManager(report.aclManager).addAssetListingAdmin(address(configEngine)); |
| 141 | + IACLManager(report.aclManager).addRiskAdmin(address(configEngine)); |
| 142 | + configEngine.listAssetsCustom(context, listings); |
| 143 | + |
| 144 | + // Log outputs for convenience |
| 145 | + console.log('Deployer', deployer); |
| 146 | + console.log('USDC', address(a.usdc)); |
| 147 | + console.log('USDT', address(a.usdt)); |
| 148 | + console.log('WBTC', address(a.wbtc)); |
| 149 | + console.log('WETH', address(a.weth)); |
| 150 | + console.log('Oracle USDC', address(a.usdcOracle)); |
| 151 | + console.log('Oracle USDT', address(a.usdtOracle)); |
| 152 | + console.log('Oracle WBTC', address(a.wbtcOracle)); |
| 153 | + console.log('Oracle WETH', address(a.wethOracle)); |
| 154 | + console.log('Sequencer oracle', address(a.sequencerOracle)); |
| 155 | + console.log('Pool', report.poolProxy); |
| 156 | + console.log('ConfigEngine', report.configEngine); |
| 157 | + console.log('AaveOracle', report.aaveOracle); |
| 158 | + |
| 159 | + // Ensure sequencer oracle timestamp is older than grace period to allow borrowing |
| 160 | + a.sequencerOracle.setAnswer(false, block.timestamp - 2 hours); |
| 161 | + |
| 162 | + _quickHealthCheck(report, deployer, a); |
| 163 | + |
| 164 | + vm.stopBroadcast(); |
| 165 | + } |
| 166 | + |
| 167 | + function _buildListing( |
| 168 | + address asset, |
| 169 | + string memory symbol, |
| 170 | + address priceFeed, |
| 171 | + IAaveV3ConfigEngine.InterestRateInputData memory rateParams, |
| 172 | + address aTokenImpl, |
| 173 | + address vTokenImpl |
| 174 | + ) internal pure returns (IAaveV3ConfigEngine.ListingWithCustomImpl memory listing) { |
| 175 | + listing.base = IAaveV3ConfigEngine.Listing({ |
| 176 | + asset: asset, |
| 177 | + assetSymbol: symbol, |
| 178 | + priceFeed: priceFeed, |
| 179 | + rateStrategyParams: rateParams, |
| 180 | + enabledToBorrow: EngineFlags.ENABLED, |
| 181 | + borrowableInIsolation: EngineFlags.DISABLED, |
| 182 | + withSiloedBorrowing: EngineFlags.DISABLED, |
| 183 | + flashloanable: EngineFlags.ENABLED, |
| 184 | + ltv: 82_50, |
| 185 | + liqThreshold: 86_00, |
| 186 | + liqBonus: 5_00, |
| 187 | + reserveFactor: 10_00, |
| 188 | + supplyCap: 0, // uncapped |
| 189 | + borrowCap: 0, // uncapped |
| 190 | + debtCeiling: 0, |
| 191 | + liqProtocolFee: 10_00 |
| 192 | + }); |
| 193 | + |
| 194 | + listing.implementations = IAaveV3ConfigEngine.TokenImplementations({ |
| 195 | + aToken: aTokenImpl, |
| 196 | + vToken: vTokenImpl |
| 197 | + }); |
| 198 | + } |
| 199 | + |
| 200 | + /// @dev Simple post-deploy sanity: supply/borrow/repay flow + price bump |
| 201 | + function _quickHealthCheck( |
| 202 | + MarketReport memory report, |
| 203 | + address deployer, |
| 204 | + DeployedAssets memory a |
| 205 | + ) internal { |
| 206 | + IPool pool = IPool(report.poolProxy); |
| 207 | + |
| 208 | + // Approvals |
| 209 | + IERC20(address(a.usdc)).approve(address(pool), type(uint256).max); |
| 210 | + IERC20(address(a.weth)).approve(address(pool), type(uint256).max); |
| 211 | + |
| 212 | + // a) Supply 1 WETH as collateral |
| 213 | + uint256 wethAmount = 1 ether; |
| 214 | + pool.supply(address(a.weth), wethAmount, deployer, 0); |
| 215 | + pool.setUserUseReserveAsCollateral(address(a.weth), true); |
| 216 | + |
| 217 | + // b) Supply 1000 USDC, keep collateral disabled |
| 218 | + uint256 usdcAmount = 1000 * 1e6; |
| 219 | + pool.supply(address(a.usdc), usdcAmount, deployer, 0); |
| 220 | + pool.setUserUseReserveAsCollateral(address(a.usdc), false); |
| 221 | + |
| 222 | + // c) Borrow 900 USDC (variable rate) |
| 223 | + uint256 borrowAmount = 900 * 1e6; |
| 224 | + pool.borrow(address(a.usdc), borrowAmount, 2, 0, deployer); |
| 225 | + |
| 226 | + // d) Repay all USDC (repay max to cover interest) |
| 227 | + pool.repay(address(a.usdc), type(uint256).max, 2, deployer); |
| 228 | + |
| 229 | + // e) Update WETH price to 3100 USD |
| 230 | + a.wethOracle.setLatestAnswer(3100e8); |
| 231 | + |
| 232 | + // Log account data |
| 233 | + ( |
| 234 | + uint256 totalCollateralBase, |
| 235 | + uint256 totalDebtBase, |
| 236 | + uint256 availableBorrowsBase, |
| 237 | + uint256 currentLiquidationThreshold, |
| 238 | + uint256 ltv, |
| 239 | + uint256 healthFactor |
| 240 | + ) = pool.getUserAccountData(deployer); |
| 241 | + |
| 242 | + console.log('Post-check collateral (base)', totalCollateralBase); |
| 243 | + console.log('Post-check debt (base)', totalDebtBase); |
| 244 | + console.log('Available borrows (base)', availableBorrowsBase); |
| 245 | + console.log('Liq threshold', currentLiquidationThreshold); |
| 246 | + console.log('LTV', ltv); |
| 247 | + console.log('Health factor', healthFactor); |
| 248 | + console.log('WETH price now', a.wethOracle.latestAnswer()); |
| 249 | + console.log('USDC price (ref currency)', a.usdcOracle.latestAnswer()); |
| 250 | + } |
| 251 | + |
| 252 | + function _getPrivateKey() internal returns (uint256) { |
| 253 | + string memory raw = vm.envString('PRIVATE_KEY'); |
| 254 | + bytes memory b = bytes(raw); |
| 255 | + bool hasPrefix = b.length >= 2 && b[0] == '0' && (b[1] == 'x' || b[1] == 'X'); |
| 256 | + string memory normalized = hasPrefix ? raw : string(abi.encodePacked('0x', raw)); |
| 257 | + return uint256(vm.parseBytes32(normalized)); |
| 258 | + } |
| 259 | +} |
0 commit comments