// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Script, console} from "forge-std/Script.sol"; import {AaveQuotePushFlashReceiver} from "../../contracts/flash/AaveQuotePushFlashReceiver.sol"; import {QuotePushTreasuryManager} from "../../contracts/flash/QuotePushTreasuryManager.sol"; interface IDODOPMMPoolQuoteManaged { function querySellQuote(address trader, uint256 payQuoteAmount) external view returns (uint256 receiveBaseAmount, uint256 mtFee); } /** * @title RunManagedMainnetAaveCwusdcUsdcQuotePushCycle * @notice Simulate or broadcast a full manager-backed cycle: * flash quote-push -> harvest retained quote into treasury manager -> split to configured recipients. * * Env: * Same flash envs as RunMainnetAaveCwusdcUsdcQuotePushOnce * QUOTE_PUSH_TREASURY_MANAGER_MAINNET required * QUOTE_PUSH_TREASURY_HARVEST optional; default 1 * QUOTE_PUSH_TREASURY_GAS_HOLDBACK_TARGET_RAW optional; default 0 * * Notes: * - Gas holdback target is a quote-denominated cap. The script computes: * gasAmount = min(manager.availableQuote(), gasHoldbackTargetRaw) * recycleAmount = manager.availableQuote() - gasAmount * - This is primarily used by the keeper dry-run so flash and recycle happen in the * same simulated environment and post-flash surplus is visible to the manager. */ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script { address internal constant DEFAULT_POOL = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E; address internal constant DEFAULT_CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298; function run() external { uint256 pk = vm.envUint("PRIVATE_KEY"); address receiver = vm.envAddress("AAVE_QUOTE_PUSH_RECEIVER_MAINNET"); address managerAddr = vm.envAddress("QUOTE_PUSH_TREASURY_MANAGER_MAINNET"); address pool = vm.envOr("POOL_CWUSDC_USDC_MAINNET", DEFAULT_POOL); address integration = vm.envAddress("DODO_PMM_INTEGRATION_MAINNET"); address baseToken = vm.envOr("CWUSDC_MAINNET", DEFAULT_CWUSDC); address usdc = vm.envOr("USDC_MAINNET", DEFAULT_USDC); address unwinder = vm.envAddress("QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET"); uint256 amount = vm.envUint("FLASH_QUOTE_AMOUNT_RAW"); uint256 localCap = vm.envOr("MAX_FLASH_QUOTE_AMOUNT_RAW", DEFENDED_SAFE_CAP_RAW); bool harvest = vm.envOr("QUOTE_PUSH_TREASURY_HARVEST", uint256(1)) == 1; uint256 gasHoldbackTargetRaw = vm.envOr("QUOTE_PUSH_TREASURY_GAS_HOLDBACK_TARGET_RAW", uint256(0)); _validateDefendedLane(pool, amount, localCap); QuotePushTreasuryManager manager = QuotePushTreasuryManager(managerAddr); AaveQuotePushFlashReceiver.QuotePushParams memory p = _loadQuotePushParams(receiver, pool, integration, baseToken, unwinder, amount); console.log("receiver", receiver); console.log("manager", managerAddr); console.log("pool", pool); console.log("amount", amount); console.log("managerAvailableBefore", manager.availableQuote()); console.log("receiverSweepableBefore", manager.receiverSweepableQuote()); console.log("gasHoldbackTargetRaw", gasHoldbackTargetRaw); vm.startBroadcast(pk); (uint256 harvested, uint256 gasAmount, uint256 recycleAmount) = manager.runManagedCycle(usdc, amount, p, harvest, gasHoldbackTargetRaw); vm.stopBroadcast(); console.log("managedCycleHarvestedRaw", harvested); console.log("managedCycleGasDistributionRaw", gasAmount); console.log("managedCycleRecycleDistributionRaw", recycleAmount); console.log("managerQuoteAfter", manager.quoteBalance()); console.log("managerAvailableAfter", manager.availableQuote()); console.log("receiverSweepableAfter", manager.receiverSweepableQuote()); } function _validateDefendedLane(address pool, uint256 amount, uint256 localCap) internal pure { require(pool == DEFAULT_POOL, "defended pool only"); require(localCap <= DEFENDED_SAFE_CAP_RAW, "local cap exceeds defended safe cap"); require(amount <= localCap, "flash amount exceeds cap"); } function _loadQuotePushParams( address receiver, address pool, address integration, address baseToken, address unwinder, uint256 amount ) internal view returns (AaveQuotePushFlashReceiver.QuotePushParams memory p) { uint256 minPmmNum = vm.envOr("MIN_OUT_PMM_NUM", uint256(985)); uint256 minPmmDen = vm.envOr("MIN_OUT_PMM_DEN", uint256(1000)); uint256 minOutPmm = vm.envOr("MIN_OUT_PMM", uint256(0)); if (minOutPmm == 0) { (uint256 baseOut,) = IDODOPMMPoolQuoteManaged(pool).querySellQuote(receiver, amount); minOutPmm = (baseOut * minPmmNum) / minPmmDen; } uint256 premiumBps = vm.envOr("AAVE_FLASH_PREMIUM_BPS", uint256(5)); uint256 buf = vm.envOr("MIN_OUT_UNWIND_BUFFER_RAW", uint256(5000)); uint256 minOutUnwind = vm.envOr("MIN_OUT_UNWIND", uint256(0)); if (minOutUnwind == 0) { uint256 premium = (amount * premiumBps) / 10000; minOutUnwind = amount + premium + buf; } uint256 unwindMode = vm.envOr("UNWIND_MODE", uint256(0)); bytes memory unwindData; if (unwindMode == 0) { uint24 fee = uint24(vm.envUint("UNWIND_V3_FEE_U24")); unwindData = abi.encode(fee); } else if (unwindMode == 1) { address dodoPool = vm.envAddress("UNWIND_DODO_POOL"); unwindData = abi.encode(dodoPool); } else if (unwindMode == 2) { string memory pathHex = vm.envString("UNWIND_V3_PATH_HEX"); bytes memory path = vm.parseBytes(pathHex); unwindData = abi.encode(path); } else if (unwindMode == 4) { address poolA = vm.envAddress("UNWIND_TWO_HOP_POOL_A"); address poolB = vm.envAddress("UNWIND_TWO_HOP_POOL_B"); address midToken = vm.envAddress("UNWIND_TWO_HOP_MID_TOKEN"); uint256 minMidOut = vm.envOr("UNWIND_MIN_MID_OUT_RAW", uint256(1)); unwindData = abi.encode(poolA, poolB, midToken, minMidOut); } else if (unwindMode == 5) { address dodoPool = vm.envAddress("UNWIND_DODO_POOL"); address intermediateToken = vm.envAddress("UNWIND_INTERMEDIATE_TOKEN"); uint256 minIntermediateOut = vm.envOr("UNWIND_MIN_INTERMEDIATE_OUT_RAW", uint256(1)); string memory pathHex = vm.envString("UNWIND_V3_PATH_HEX"); bytes memory path = vm.parseBytes(pathHex); unwindData = abi.encode(dodoPool, intermediateToken, minIntermediateOut, path); } else if (unwindMode == 6) { address poolA = vm.envAddress("UNWIND_TWO_HOP_POOL_A"); address poolB = vm.envAddress("UNWIND_TWO_HOP_POOL_B"); address midToken = vm.envAddress("UNWIND_TWO_HOP_MID_TOKEN"); uint256 minMidOut = vm.envOr("UNWIND_MIN_MID_OUT_RAW", uint256(1)); address intermediateToken = vm.envAddress("UNWIND_INTERMEDIATE_TOKEN"); uint256 minIntermediateOut = vm.envOr("UNWIND_MIN_INTERMEDIATE_OUT_RAW", uint256(1)); string memory pathHex = vm.envString("UNWIND_V3_PATH_HEX"); bytes memory path = vm.parseBytes(pathHex); unwindData = abi.encode(poolA, poolB, midToken, minMidOut, intermediateToken, minIntermediateOut, path); } else { revert("UNWIND_MODE must be 0, 1, 2, 4, 5, or 6"); } p = AaveQuotePushFlashReceiver.QuotePushParams({ integration: integration, pmmPool: pool, baseToken: baseToken, externalUnwinder: unwinder, minOutPmm: minOutPmm, minOutUnwind: minOutUnwind, unwindData: unwindData, atomicBridge: AaveQuotePushFlashReceiver.AtomicBridgeParams({ coordinator: address(0), sourceChain: 0, destinationChain: 0, destinationAsset: address(0), bridgeAmount: 0, minDestinationAmount: 0, destinationRecipient: address(0), destinationDeadline: 0, routeId: bytes32(0), settlementMode: bytes32(0), submitCommitment: false }) }); console.log("minOutPmm", minOutPmm); console.log("minOutUnwind", minOutUnwind); } }