WIP: Chain138 deployment scripts, flash receivers, HYBX OMNL recovery

This commit is contained in:
defiQUG
2026-06-02 06:09:44 -07:00
parent e1560a880b
commit f04a7cb7c8
35 changed files with 2279 additions and 83 deletions

View File

@@ -0,0 +1,125 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
AaveUniV2CwStableRebalanceFlashReceiver
} from "../../contracts/flash/AaveUniV2CwStableRebalanceFlashReceiver.sol";
/**
* @title RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove
* @notice Simulate or broadcast flash rebalance + LP remove on mainnet cWUSDC/USDC UniV2.
*
* Prerequisite: run plan-mainnet-cwusdc-usdc-univ2-flash-rebalance-remove.py and transfer LP
* to the receiver (or set UNIV2_FLASH_PULL_LP=1 with prior LP approval to receiver).
*
* Env:
* PRIVATE_KEY
* ETHEREUM_MAINNET_RPC
* UNIV2_FLASH_REBALANCE_RECEIVER_MAINNET
* UNIV2_FLASH_REBALANCE_PLAN_JSON optional path to planner JSON
*/
contract RunMainnetUniV2CwusdcUsdcFlashRebalanceRemove is Script {
address internal constant DEFAULT_PAIR = 0xC28706F899266b36BC43cc072b3a921BDf2C48D9;
address internal constant DEFAULT_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address internal constant DEFAULT_CW = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address receiver = vm.envAddress("UNIV2_FLASH_REBALANCE_RECEIVER_MAINNET");
string memory planPath = vm.envOr(
"UNIV2_FLASH_REBALANCE_PLAN_JSON",
string("reports/status/mainnet-cwusdc-usdc-univ2-flash-rebalance-plan-latest.json")
);
(
address pair,
address router,
address cw,
address usdc,
address lpHolder,
address recipient,
uint256 lpAmount,
uint256 flashStableIn,
uint256 minCwRebalance,
uint256 minCwRemove,
uint256 minStableRemove,
uint256 cwToSell,
uint256 minStableRepay
) = _loadPlan(planPath);
pair = pair == address(0) ? DEFAULT_PAIR : pair;
router = router == address(0) ? DEFAULT_ROUTER : router;
cw = cw == address(0) ? DEFAULT_CW : cw;
usdc = usdc == address(0) ? DEFAULT_USDC : usdc;
console.log("receiver", receiver);
console.log("pair", pair);
console.log("lpHolder", lpHolder);
console.log("lpAmount", lpAmount);
console.log("flashStableIn", flashStableIn);
console.log("receiverLpBefore", IERC20(pair).balanceOf(receiver));
vm.startBroadcast(pk);
if (vm.envOr("UNIV2_FLASH_PULL_LP", uint256(0)) == 1) {
AaveUniV2CwStableRebalanceFlashReceiver(receiver).pullLpFrom(pair, lpHolder, lpAmount);
}
AaveUniV2CwStableRebalanceFlashReceiver.RebalanceRemoveParams memory p =
AaveUniV2CwStableRebalanceFlashReceiver.RebalanceRemoveParams({
router: router,
pair: pair,
cwToken: cw,
stableToken: usdc,
lpAmount: lpAmount,
rebalanceStableIn: flashStableIn,
minCwFromRebalance: minCwRebalance,
minStableFromRemove: minStableRemove,
minCwFromRemove: minCwRemove,
cwToSellForRepay: cwToSell,
minStableFromRepaySwap: minStableRepay,
recipient: recipient
});
AaveUniV2CwStableRebalanceFlashReceiver(receiver).runRebalanceRemove(usdc, flashStableIn, p);
vm.stopBroadcast();
console.log("recipientStableAfter", IERC20(usdc).balanceOf(recipient));
console.log("recipientCwAfter", IERC20(cw).balanceOf(recipient));
}
function _loadPlan(string memory path)
internal
view
returns (
address pair,
address router,
address cw,
address usdc,
address lpHolder,
address recipient,
uint256 lpAmount,
uint256 flashStableIn,
uint256 minCwRebalance,
uint256 minCwRemove,
uint256 minStableRemove,
uint256 cwToSell,
uint256 minStableRepay
)
{
string memory json = vm.readFile(path);
pair = vm.parseJsonAddress(json, ".pair");
router = vm.parseJsonAddress(json, ".router");
cw = vm.parseJsonAddress(json, ".cwToken");
usdc = vm.parseJsonAddress(json, ".stableToken");
lpHolder = vm.parseJsonAddress(json, ".lpHolder");
recipient = vm.parseJsonAddress(json, ".recipient");
lpAmount = vm.parseJsonUint(json, ".lpAmountRaw");
flashStableIn = vm.parseJsonUint(json, ".flashLoan.stableBorrowRaw");
minCwRebalance = vm.parseJsonUint(json, ".rebalanceSwap.minCwOutRaw");
minCwRemove = vm.parseJsonUint(json, ".removeLiquidity.minCwOutRaw");
minStableRemove = vm.parseJsonUint(json, ".removeLiquidity.minStableOutRaw");
cwToSell = vm.parseJsonUint(json, ".repaySwap.cwToSellRaw");
minStableRepay = vm.parseJsonUint(json, ".repaySwap.minStableOutRaw");
}
}

View File

@@ -0,0 +1,101 @@
// 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 IDODOPMMPoolQuoteManagedUsdt {
function querySellQuote(address trader, uint256 payQuoteAmount) external view returns (uint256 receiveBaseAmount, uint256 mtFee);
}
/// @notice USDT rail mirror of RunManagedMainnetAaveCwusdcUsdcQuotePushCycle.
contract RunManagedMainnetAaveCwusdtUsdtQuotePushCycle is Script {
address internal constant DEFAULT_POOL = 0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC;
address internal constant DEFAULT_CWUSDT = 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE;
address internal constant DEFAULT_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
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_CWUSDT_USDT_MAINNET", DEFAULT_POOL);
address integration = vm.envAddress("DODO_PMM_INTEGRATION_MAINNET");
address baseToken = vm.envOr("CWUSDT_MAINNET", DEFAULT_CWUSDT);
address usdt = vm.envOr("USDT_MAINNET", DEFAULT_USDT);
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));
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");
QuotePushTreasuryManager manager = QuotePushTreasuryManager(managerAddr);
AaveQuotePushFlashReceiver.QuotePushParams memory p =
_loadQuotePushParams(receiver, pool, integration, baseToken, unwinder, amount);
vm.startBroadcast(pk);
(uint256 harvested, uint256 gasAmount, uint256 recycleAmount) =
manager.runManagedCycle(usdt, amount, p, harvest, gasHoldbackTargetRaw);
vm.stopBroadcast();
console.log("managedCycleHarvestedRaw", harvested);
console.log("managedCycleGasDistributionRaw", gasAmount);
console.log("managedCycleRecycleDistributionRaw", recycleAmount);
}
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,) = IDODOPMMPoolQuoteManagedUsdt(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 premium = (amount * premiumBps) / 10000;
uint256 minOutUnwind = vm.envOr("MIN_OUT_UNWIND", amount + premium + buf);
uint256 unwindMode = vm.envOr("UNWIND_MODE", uint256(1));
bytes memory unwindData;
if (unwindMode == 1) {
address dodoPool = vm.envOr("UNWIND_DODO_POOL", pool);
unwindData = abi.encode(dodoPool);
} else {
revert("USDT rail: UNWIND_MODE=1 (DODO) required for now");
}
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
})
});
}
}

View File

@@ -0,0 +1,90 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface IDODOPMMIntegrationSeed {
function isRegisteredPool(address pool) external view returns (bool);
function addLiquidity(address pool, uint256 baseAmount, uint256 quoteAmount)
external
returns (uint256 baseShare, uint256 quoteShare, uint256 lpShare);
}
interface IDODOPMMPoolSeed {
function _BASE_TOKEN_() external view returns (address);
function _QUOTE_TOKEN_() external view returns (address);
function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve);
}
/// @notice Permanently seed defended mainnet cWUSDC/USDC or cWUSDT/USDT DODO pools via addLiquidity.
/// @dev Uses deployer inventory — not flash (flash must repay same block). Pair with Aave borrow + vault deposit off-chain.
contract SeedMainnetCwStablePoolPermanent is Script {
using SafeERC20 for IERC20;
address internal constant DEFAULT_INTEGRATION = 0xa9F284eD010f4F7d7F8F201742b49b9f58e29b84;
address internal constant POOL_CWUSDC_USDC = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E;
/// @dev Registered on DODOPMMIntegration.pools(cWUSDT, USDT); 0x99d012… is an unregistered duplicate.
address internal constant POOL_CWUSDT_USDT = 0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC;
address internal constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
address internal constant CWUSDT = 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE;
address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
string memory rail = vm.envOr("CW_STABLE_RAIL", string("USDC"));
uint256 baseAmount = vm.envUint("SEED_BASE_AMOUNT_RAW");
uint256 quoteAmount = vm.envUint("SEED_QUOTE_AMOUNT_RAW");
address integration = vm.envOr("DODO_PMM_INTEGRATION_MAINNET", DEFAULT_INTEGRATION);
(address pool, address baseToken, address quoteToken) = _resolveRail(rail);
IDODOPMMPoolSeed poolView = IDODOPMMPoolSeed(pool);
require(poolView._BASE_TOKEN_() == baseToken, "base mismatch");
require(poolView._QUOTE_TOKEN_() == quoteToken, "quote mismatch");
require(IDODOPMMIntegrationSeed(integration).isRegisteredPool(pool), "pool not registered on integration");
(uint256 baseBefore, uint256 quoteBefore) = poolView.getVaultReserve();
console.log("deployer", deployer);
console.log("rail", rail);
console.log("pool", pool);
console.log("baseAmount", baseAmount);
console.log("quoteAmount", quoteAmount);
console.log("baseReserveBefore", baseBefore);
console.log("quoteReserveBefore", quoteBefore);
vm.startBroadcast(pk);
IERC20(baseToken).forceApprove(integration, baseAmount);
IERC20(quoteToken).forceApprove(integration, quoteAmount);
(uint256 baseShare, uint256 quoteShare, uint256 lpShare) =
IDODOPMMIntegrationSeed(integration).addLiquidity(pool, baseAmount, quoteAmount);
IERC20(baseToken).forceApprove(integration, 0);
IERC20(quoteToken).forceApprove(integration, 0);
vm.stopBroadcast();
(uint256 baseAfter, uint256 quoteAfter) = poolView.getVaultReserve();
console.log("baseShare", baseShare);
console.log("quoteShare", quoteShare);
console.log("lpShare", lpShare);
console.log("baseReserveAfter", baseAfter);
console.log("quoteReserveAfter", quoteAfter);
}
function _resolveRail(string memory rail)
internal
pure
returns (address pool, address baseToken, address quoteToken)
{
bytes32 key = keccak256(bytes(rail));
if (key == keccak256(bytes("USDC"))) {
return (POOL_CWUSDC_USDC, CWUSDC, USDC);
}
if (key == keccak256(bytes("USDT"))) {
return (POOL_CWUSDT_USDT, CWUSDT, USDT);
}
revert("CW_STABLE_RAIL must be USDC or USDT");
}
}