From 49740f1a598dbaad45d73de3ad3fe9360f931f6a Mon Sep 17 00:00:00 2001 From: defiQUG Date: Mon, 13 Apr 2026 21:38:57 -0700 Subject: [PATCH] Harden quote-push operator workflows --- ...deploy-mainnet-aave-quote-push-receiver.sh | 15 ++- .../deploy-mainnet-aave-quote-push-stack.sh | 19 ++- ...loy-mainnet-quote-push-treasury-manager.sh | 62 +++++++++- .../manage-mainnet-quote-push-treasury.sh | 6 +- ...innet-cwusdc-flash-quote-push-rebalance.sh | 1 + ...run-mainnet-aave-cwusdc-quote-push-once.sh | 37 +++++- .../run-mainnet-aave-quote-push-keeper.sh | 116 +++++++++++++++++- ...n-mainnet-aave-quote-push-managed-cycle.sh | 93 +++++++++++++- 8 files changed, 331 insertions(+), 18 deletions(-) diff --git a/scripts/deployment/deploy-mainnet-aave-quote-push-receiver.sh b/scripts/deployment/deploy-mainnet-aave-quote-push-receiver.sh index b47037a5..87bca483 100644 --- a/scripts/deployment/deploy-mainnet-aave-quote-push-receiver.sh +++ b/scripts/deployment/deploy-mainnet-aave-quote-push-receiver.sh @@ -49,11 +49,21 @@ require_env() { fi } +resolved_chain_id() { + if [[ -n "${ETHEREUM_MAINNET_RPC:-}" ]] && command -v cast >/dev/null 2>&1; then + cast chain-id --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null | awk '{print $1}' + return 0 + fi + echo "1" +} + pick_latest_receiver() { local mode="$1" - local latest_json="${SMOM}/broadcast/DeployAaveQuotePushFlashReceiver.s.sol/1/run-latest.json" + local chain_id + chain_id="$(resolved_chain_id)" + local latest_json="${SMOM}/broadcast/DeployAaveQuotePushFlashReceiver.s.sol/${chain_id}/run-latest.json" if [[ "$mode" == "dry-run" ]]; then - latest_json="${SMOM}/broadcast/DeployAaveQuotePushFlashReceiver.s.sol/1/dry-run/run-latest.json" + latest_json="${SMOM}/broadcast/DeployAaveQuotePushFlashReceiver.s.sol/${chain_id}/dry-run/run-latest.json" fi if [[ ! -f "$latest_json" ]] || ! command -v jq >/dev/null 2>&1; then return 1 @@ -62,6 +72,7 @@ pick_latest_receiver() { "$latest_json" | tail -n1 } +require_cmd cast require_cmd forge MODE="dry-run" diff --git a/scripts/deployment/deploy-mainnet-aave-quote-push-stack.sh b/scripts/deployment/deploy-mainnet-aave-quote-push-stack.sh index c3e74ce9..23ea3cd0 100755 --- a/scripts/deployment/deploy-mainnet-aave-quote-push-stack.sh +++ b/scripts/deployment/deploy-mainnet-aave-quote-push-stack.sh @@ -9,7 +9,7 @@ set -euo pipefail # PRIVATE_KEY, ETHEREUM_MAINNET_RPC # DODO_PMM_INTEGRATION_MAINNET required when QUOTE_PUSH_UNWINDER_TYPE=dodo # UNISWAP_V3_SWAP_ROUTER_MAINNET optional for univ3; defaults to legacy SwapRouter `0xE592...` -# QUOTE_PUSH_UNWINDER_TYPE univ3 (default) | dodo | two_hop_dodo | dodo_univ3 +# QUOTE_PUSH_UNWINDER_TYPE univ3 (default) | dodo | two_hop_dodo | dodo_univ3 | two_hop_dodo_univ3 # # Usage: # source scripts/lib/load-project-env.sh @@ -132,6 +132,19 @@ elif [[ "$UNW" == "dodo_univ3" ]]; then "${BROADCAST[@]}" \ -vvvv ) +elif [[ "$UNW" == "two_hop_dodo_univ3" ]]; then + if [[ -z "${DODO_PMM_INTEGRATION_MAINNET:-}" ]]; then + echo "[fail] DODO_PMM_INTEGRATION_MAINNET required for two_hop_dodo_univ3 unwinder" >&2 + exit 1 + fi + echo "Step 2/2: DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder" + ( + cd "$SMOM" + forge script script/deploy/DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder.s.sol:DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder \ + --rpc-url "$ETHEREUM_MAINNET_RPC" \ + "${BROADCAST[@]}" \ + -vvvv + ) else echo "Step 2/2: DeployUniswapV3ExternalUnwinder" ( @@ -160,6 +173,10 @@ case "$UNW" in unwinder_contract="DODOToUniswapV3MultiHopExternalUnwinder" unwinder_script="DeployDODOToUniswapV3MultiHopExternalUnwinder.s.sol" ;; + two_hop_dodo_univ3) + unwinder_contract="TwoHopDodoToUniswapV3MultiHopExternalUnwinder" + unwinder_script="DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder.s.sol" + ;; esac unwinder_addr="$(pick_latest_create_address "$unwinder_script" "$unwinder_contract" || true)" diff --git a/scripts/deployment/deploy-mainnet-quote-push-treasury-manager.sh b/scripts/deployment/deploy-mainnet-quote-push-treasury-manager.sh index 90c7b60e..cc76d053 100644 --- a/scripts/deployment/deploy-mainnet-quote-push-treasury-manager.sh +++ b/scripts/deployment/deploy-mainnet-quote-push-treasury-manager.sh @@ -72,13 +72,23 @@ require_env() { fi } +resolved_chain_id() { + if [[ -n "${ETHEREUM_MAINNET_RPC:-}" ]] && command -v cast >/dev/null 2>&1; then + cast chain-id --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null | awk '{print $1}' + return 0 + fi + echo "1" +} + pick_latest_create_address() { local script_name="$1" local contract_name="$2" local mode="${3:-apply}" - local latest_json="${SMOM}/broadcast/${script_name}/1/run-latest.json" + local chain_id + chain_id="$(resolved_chain_id)" + local latest_json="${SMOM}/broadcast/${script_name}/${chain_id}/run-latest.json" if [[ "$mode" == "dry-run" ]]; then - latest_json="${SMOM}/broadcast/${script_name}/1/dry-run/run-latest.json" + latest_json="${SMOM}/broadcast/${script_name}/${chain_id}/dry-run/run-latest.json" fi if [[ ! -f "$latest_json" ]] || ! command -v jq >/dev/null 2>&1; then return 1 @@ -88,6 +98,7 @@ pick_latest_create_address() { "$latest_json" | tail -n1 } +require_cmd cast require_cmd forge MODE="dry-run" @@ -105,6 +116,7 @@ done require_env PRIVATE_KEY require_env ETHEREUM_MAINNET_RPC +DEPLOYER="$(cast wallet address --private-key "$PRIVATE_KEY")" if [[ -z "${AAVE_QUOTE_PUSH_RECEIVER_MAINNET:-}" ]]; then inferred_receiver="$(pick_latest_create_address "DeployAaveQuotePushFlashReceiver.s.sol" "AaveQuotePushFlashReceiver" "apply" || true)" if [[ -n "$inferred_receiver" && "$inferred_receiver" != "null" ]]; then @@ -113,7 +125,26 @@ if [[ -z "${AAVE_QUOTE_PUSH_RECEIVER_MAINNET:-}" ]]; then fi require_env AAVE_QUOTE_PUSH_RECEIVER_MAINNET +if [[ "${QUOTE_PUSH_TREASURY_TAKE_RECEIVER_OWNERSHIP:-0}" == "1" ]]; then + current_receiver_owner="$(cast call "$AAVE_QUOTE_PUSH_RECEIVER_MAINNET" 'owner()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null | awk '{print $1}' || true)" + if [[ -z "$current_receiver_owner" ]]; then + echo "[fail] receiver does not expose owner(): $AAVE_QUOTE_PUSH_RECEIVER_MAINNET" >&2 + exit 1 + fi + if [[ "${current_receiver_owner,,}" != "${DEPLOYER,,}" ]]; then + cat >&2 <&2 <&2 </dev/null || true @@ -39,8 +41,9 @@ source "${SMOM}/scripts/load-env.sh" >/dev/null 2>&1 || true [[ -n "$_qp_harvest" ]] && export QUOTE_PUSH_TREASURY_HARVEST="$_qp_harvest" [[ -n "$_qp_gas_raw" ]] && export QUOTE_PUSH_TREASURY_GAS_DISTRIBUTION_RAW="$_qp_gas_raw" [[ -n "$_qp_recycle_raw" ]] && export QUOTE_PUSH_TREASURY_RECYCLE_DISTRIBUTION_RAW="$_qp_recycle_raw" +[[ -n "$_qp_transfer_receiver_owner_to" ]] && export QUOTE_PUSH_TREASURY_TRANSFER_RECEIVER_OWNER_TO="$_qp_transfer_receiver_owner_to" -unset _qp_private_key _qp_rpc _qp_manager _qp_harvest _qp_gas_raw _qp_recycle_raw +unset _qp_private_key _qp_rpc _qp_manager _qp_harvest _qp_gas_raw _qp_recycle_raw _qp_transfer_receiver_owner_to require_cmd() { command -v "$1" >/dev/null 2>&1 || { @@ -111,6 +114,7 @@ echo "manager_operator=$manager_operator" echo "harvest=${QUOTE_PUSH_TREASURY_HARVEST:-1}" echo "gas_distribution_raw=${QUOTE_PUSH_TREASURY_GAS_DISTRIBUTION_RAW:-0}" echo "recycle_distribution_raw=${QUOTE_PUSH_TREASURY_RECYCLE_DISTRIBUTION_RAW:-0}" +echo "transfer_receiver_owner_to=${QUOTE_PUSH_TREASURY_TRANSFER_RECEIVER_OWNER_TO:-0x0000000000000000000000000000000000000000}" echo "quote_balance_raw=$quote_balance" echo "available_quote_raw=$available_quote" echo "receiver_sweepable_raw=$receiver_sweepable" diff --git a/scripts/deployment/plan-mainnet-cwusdc-flash-quote-push-rebalance.sh b/scripts/deployment/plan-mainnet-cwusdc-flash-quote-push-rebalance.sh index 6e277183..8d1e6f7a 100755 --- a/scripts/deployment/plan-mainnet-cwusdc-flash-quote-push-rebalance.sh +++ b/scripts/deployment/plan-mainnet-cwusdc-flash-quote-push-rebalance.sh @@ -98,6 +98,7 @@ echo "3) On-chain stack (forge, from repo root):" echo " bash scripts/deployment/deploy-mainnet-aave-quote-push-stack.sh --dry-run # then --apply" echo " bash scripts/deployment/run-mainnet-aave-cwusdc-quote-push-once.sh --dry-run" echo " FLASH_LOOP_COUNT=3 bash scripts/deployment/run-mainnet-aave-cwusdc-quote-push-loop.sh --apply" +echo " Route ladder on a local fork: bash scripts/verify/benchmark-mainnet-aave-quote-push-routes.sh" echo " Solidity: smom-dbis-138/script/flash/RunMainnetAaveCwusdcUsdcQuotePushOnce.s.sol" echo " Fork proofs: cd smom-dbis-138 && forge test --match-contract AaveQuotePushFlashReceiverMainnetForkTest" echo diff --git a/scripts/deployment/run-mainnet-aave-cwusdc-quote-push-once.sh b/scripts/deployment/run-mainnet-aave-cwusdc-quote-push-once.sh index 5c5a5447..d6f70988 100755 --- a/scripts/deployment/run-mainnet-aave-cwusdc-quote-push-once.sh +++ b/scripts/deployment/run-mainnet-aave-cwusdc-quote-push-once.sh @@ -13,6 +13,8 @@ set -euo pipefail # UNWIND_MODE: 0 = V3 single-hop fee; 1 = DODO pool; 2 = V3 exactInput path (UNWIND_V3_PATH_HEX); # 4 = TwoHopDodoIntegrationUnwinder (UNWIND_TWO_HOP_*) # 5 = DODOToUniswapV3MultiHopExternalUnwinder (UNWIND_DODO_POOL + UNWIND_INTERMEDIATE_TOKEN + UNWIND_V3_PATH_HEX) +# 6 = TwoHopDodoToUniswapV3MultiHopExternalUnwinder +# (UNWIND_TWO_HOP_* + UNWIND_INTERMEDIATE_TOKEN + UNWIND_V3_PATH_HEX) # # Usage: # source scripts/lib/load-project-env.sh @@ -41,6 +43,7 @@ _qp_mid_token="${UNWIND_TWO_HOP_MID_TOKEN-}" _qp_min_mid_out="${UNWIND_MIN_MID_OUT_RAW-}" _qp_min_out_pmm="${MIN_OUT_PMM-}" _qp_min_out_unwind="${MIN_OUT_UNWIND-}" +_qp_min_out_unwind_buffer="${MIN_OUT_UNWIND_BUFFER_RAW-}" _qp_fee_u24="${UNWIND_V3_FEE_U24-}" _qp_dodo_pool="${UNWIND_DODO_POOL-}" _qp_v3_path="${UNWIND_V3_PATH_HEX-}" @@ -67,6 +70,7 @@ source "${SMOM}/scripts/load-env.sh" >/dev/null 2>&1 || true [[ -n "$_qp_min_mid_out" ]] && export UNWIND_MIN_MID_OUT_RAW="$_qp_min_mid_out" [[ -n "$_qp_min_out_pmm" ]] && export MIN_OUT_PMM="$_qp_min_out_pmm" [[ -n "$_qp_min_out_unwind" ]] && export MIN_OUT_UNWIND="$_qp_min_out_unwind" +[[ -n "$_qp_min_out_unwind_buffer" ]] && export MIN_OUT_UNWIND_BUFFER_RAW="$_qp_min_out_unwind_buffer" [[ -n "$_qp_fee_u24" ]] && export UNWIND_V3_FEE_U24="$_qp_fee_u24" [[ -n "$_qp_dodo_pool" ]] && export UNWIND_DODO_POOL="$_qp_dodo_pool" [[ -n "$_qp_v3_path" ]] && export UNWIND_V3_PATH_HEX="$_qp_v3_path" @@ -75,7 +79,8 @@ source "${SMOM}/scripts/load-env.sh" >/dev/null 2>&1 || true unset _qp_private_key _qp_rpc _qp_receiver _qp_unwinder _qp_amount _qp_unwind_type _qp_unwind_mode unset _qp_pool _qp_integration _qp_pool_a _qp_pool_b _qp_mid_token _qp_min_mid_out _qp_min_out_pmm -unset _qp_min_out_unwind _qp_fee_u24 _qp_dodo_pool _qp_v3_path _qp_intermediate_token _qp_min_intermediate_out +unset _qp_min_out_unwind _qp_min_out_unwind_buffer _qp_fee_u24 _qp_dodo_pool _qp_v3_path +unset _qp_intermediate_token _qp_min_intermediate_out BROADCAST=() if (($# == 0)); then @@ -168,6 +173,13 @@ pick_default_unwinder() { return 0 fi + addr="$(pick_latest_create_address "DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder.s.sol" "TwoHopDodoToUniswapV3MultiHopExternalUnwinder" || true)" + if [[ -n "$addr" && "$addr" != "null" ]]; then + PICK_DEFAULT_UNWINDER_ADDR="$addr" + PICK_DEFAULT_UNWINDER_MODE="6" + return 0 + fi + return 1 } @@ -213,8 +225,13 @@ if [[ -z "${QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET:-}" && -n "$UNW" ]]; then unwind_contract="DODOToUniswapV3MultiHopExternalUnwinder" export UNWIND_MODE="${UNWIND_MODE:-5}" ;; + two_hop_dodo_univ3) + unwind_script="DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder.s.sol" + unwind_contract="TwoHopDodoToUniswapV3MultiHopExternalUnwinder" + export UNWIND_MODE="${UNWIND_MODE:-6}" + ;; *) - echo "[fail] QUOTE_PUSH_UNWINDER_TYPE must be univ3, dodo, two_hop_dodo, or dodo_univ3 when set" >&2 + echo "[fail] QUOTE_PUSH_UNWINDER_TYPE must be univ3, dodo, two_hop_dodo, dodo_univ3, or two_hop_dodo_univ3 when set" >&2 exit 1 ;; esac @@ -243,6 +260,14 @@ elif [[ "${UNWIND_MODE:-}" == "5" ]]; then export UNWIND_DODO_POOL="${UNWIND_DODO_POOL:-0xCC0fd27A40775c9AfcD2BBd3f7c902b0192c247A}" export UNWIND_INTERMEDIATE_TOKEN="${UNWIND_INTERMEDIATE_TOKEN:-0xdAC17F958D2ee523a2206206994597C13D831ec7}" export UNWIND_MIN_INTERMEDIATE_OUT_RAW="${UNWIND_MIN_INTERMEDIATE_OUT_RAW:-1}" +elif [[ "${UNWIND_MODE:-}" == "6" ]]; then + export UNWIND_TWO_HOP_POOL_A="${UNWIND_TWO_HOP_POOL_A:-0xe944b7Cb012A0820c07f54D51e92f0e1C74168DB}" + export UNWIND_TWO_HOP_POOL_B="${UNWIND_TWO_HOP_POOL_B:-0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC}" + export UNWIND_TWO_HOP_MID_TOKEN="${UNWIND_TWO_HOP_MID_TOKEN:-0xaF5017d0163ecb99d9B5D94e3b4D7b09Af44D8AE}" + export UNWIND_INTERMEDIATE_TOKEN="${UNWIND_INTERMEDIATE_TOKEN:-0xdAC17F958D2ee523a2206206994597C13D831ec7}" + export UNWIND_MIN_MID_OUT_RAW="${UNWIND_MIN_MID_OUT_RAW:-1}" + export UNWIND_MIN_INTERMEDIATE_OUT_RAW="${UNWIND_MIN_INTERMEDIATE_OUT_RAW:-1}" + export MIN_OUT_UNWIND_BUFFER_RAW="${MIN_OUT_UNWIND_BUFFER_RAW:-0}" fi require ETHEREUM_MAINNET_RPC @@ -267,8 +292,14 @@ elif [[ "$UM" == "5" ]]; then require UNWIND_DODO_POOL require UNWIND_INTERMEDIATE_TOKEN require UNWIND_V3_PATH_HEX +elif [[ "$UM" == "6" ]]; then + require UNWIND_TWO_HOP_POOL_A + require UNWIND_TWO_HOP_POOL_B + require UNWIND_TWO_HOP_MID_TOKEN + require UNWIND_INTERMEDIATE_TOKEN + require UNWIND_V3_PATH_HEX else - echo "[fail] UNWIND_MODE must be 0, 1, 2, 4, or 5" >&2 + echo "[fail] UNWIND_MODE must be 0, 1, 2, 4, 5, or 6" >&2 exit 1 fi diff --git a/scripts/deployment/run-mainnet-aave-quote-push-keeper.sh b/scripts/deployment/run-mainnet-aave-quote-push-keeper.sh index 0497e8b5..e06f9a2a 100644 --- a/scripts/deployment/run-mainnet-aave-quote-push-keeper.sh +++ b/scripts/deployment/run-mainnet-aave-quote-push-keeper.sh @@ -17,6 +17,7 @@ set -euo pipefail # QUOTE_PUSH_DEPLOYER_GAS_FLOOR_ETH optional; defaults to 0.003 # QUOTE_PUSH_OPERATION_BUFFER_ETH optional; defaults to 0.0005 # QUOTE_PUSH_NATIVE_TOKEN_PRICE optional; defaults to 3200 +# QUOTE_PUSH_KEEPER_ALLOW_NET_NEGATIVE optional; default 0; set to 1 to bypass modeled net gate # QUOTE_PUSH_KEEPER_SKIP_FLASH optional; default 0 # QUOTE_PUSH_KEEPER_SKIP_RECYCLE optional; default 0 # @@ -37,6 +38,7 @@ _qp_receiver_reserve="${QUOTE_PUSH_RECEIVER_RESERVE_RAW-}" _qp_gas_floor="${QUOTE_PUSH_DEPLOYER_GAS_FLOOR_ETH-}" _qp_gas_buffer="${QUOTE_PUSH_OPERATION_BUFFER_ETH-}" _qp_native_price="${QUOTE_PUSH_NATIVE_TOKEN_PRICE-}" +_qp_allow_net_negative="${QUOTE_PUSH_KEEPER_ALLOW_NET_NEGATIVE-}" _qp_skip_flash="${QUOTE_PUSH_KEEPER_SKIP_FLASH-}" _qp_skip_recycle="${QUOTE_PUSH_KEEPER_SKIP_RECYCLE-}" @@ -53,11 +55,12 @@ source "${SMOM}/scripts/load-env.sh" >/dev/null 2>&1 || true [[ -n "$_qp_gas_floor" ]] && export QUOTE_PUSH_DEPLOYER_GAS_FLOOR_ETH="$_qp_gas_floor" [[ -n "$_qp_gas_buffer" ]] && export QUOTE_PUSH_OPERATION_BUFFER_ETH="$_qp_gas_buffer" [[ -n "$_qp_native_price" ]] && export QUOTE_PUSH_NATIVE_TOKEN_PRICE="$_qp_native_price" +[[ -n "$_qp_allow_net_negative" ]] && export QUOTE_PUSH_KEEPER_ALLOW_NET_NEGATIVE="$_qp_allow_net_negative" [[ -n "$_qp_skip_flash" ]] && export QUOTE_PUSH_KEEPER_SKIP_FLASH="$_qp_skip_flash" [[ -n "$_qp_skip_recycle" ]] && export QUOTE_PUSH_KEEPER_SKIP_RECYCLE="$_qp_skip_recycle" unset _qp_private_key _qp_rpc _qp_manager _qp_token _qp_receiver_reserve _qp_gas_floor -unset _qp_gas_buffer _qp_native_price _qp_skip_flash _qp_skip_recycle +unset _qp_gas_buffer _qp_native_price _qp_allow_net_negative _qp_skip_flash _qp_skip_recycle require_cmd() { command -v "$1" >/dev/null 2>&1 || { @@ -171,6 +174,43 @@ PY KEEPER_RECYCLE_RECIPIENT="${recycle_recipient:-}" } +parse_managed_cycle_summary() { + local summary_line="$1" + local native_price="$2" + + eval "$( + python3 - "$summary_line" "$native_price" <<'PY' +import math +import sys + +line = sys.argv[1] +native_price = float(sys.argv[2]) +parts = {} +for token in line.split(): + if "=" not in token: + continue + key, value = token.split("=", 1) + parts[key] = value + +harvested_raw = int(parts.get("harvested_raw", "0")) +gas_distribution_raw = int(parts.get("gas_distribution_raw", "0")) +recycle_distribution_raw = int(parts.get("recycle_distribution_raw", "0")) +estimated_eth_required = float(parts.get("estimated_eth_required", "0") or 0) +estimated_total_gas = int(parts.get("estimated_total_gas", "0") or 0) +modeled_gas_cost_quote_raw = math.ceil(estimated_eth_required * native_price * 1_000_000) +modeled_net_quote_raw = recycle_distribution_raw - modeled_gas_cost_quote_raw + +print(f"KEEPER_MANAGED_HARVESTED_RAW={harvested_raw}") +print(f"KEEPER_MANAGED_GAS_DISTRIBUTION_RAW={gas_distribution_raw}") +print(f"KEEPER_MANAGED_RECYCLE_DISTRIBUTION_RAW={recycle_distribution_raw}") +print(f"KEEPER_MANAGED_ESTIMATED_ETH_REQUIRED={estimated_eth_required}") +print(f"KEEPER_MANAGED_ESTIMATED_TOTAL_GAS={estimated_total_gas}") +print(f"KEEPER_MANAGED_GAS_COST_QUOTE_RAW={modeled_gas_cost_quote_raw}") +print(f"KEEPER_MANAGED_NET_QUOTE_RAW={modeled_net_quote_raw}") +PY + )" +} + require_cmd cast require_cmd python3 require_cmd forge @@ -200,14 +240,23 @@ if [[ -z "${QUOTE_PUSH_TREASURY_MANAGER_MAINNET:-}" ]]; then fi if [[ -z "${AAVE_QUOTE_PUSH_RECEIVER_MAINNET:-}" ]]; then - inferred_receiver="$(pick_latest_receiver || true)" - if [[ -n "$inferred_receiver" && "$inferred_receiver" != "null" ]]; then - export AAVE_QUOTE_PUSH_RECEIVER_MAINNET="$inferred_receiver" + if [[ -n "${QUOTE_PUSH_TREASURY_MANAGER_MAINNET:-}" ]]; then + manager_receiver="$(cast call "$QUOTE_PUSH_TREASURY_MANAGER_MAINNET" 'receiver()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null | awk '{print $1}' || true)" + if [[ -n "$manager_receiver" && "$manager_receiver" != "0x0000000000000000000000000000000000000000" ]]; then + export AAVE_QUOTE_PUSH_RECEIVER_MAINNET="$manager_receiver" + fi + fi + if [[ -z "${AAVE_QUOTE_PUSH_RECEIVER_MAINNET:-}" ]]; then + inferred_receiver="$(pick_latest_receiver || true)" + if [[ -n "$inferred_receiver" && "$inferred_receiver" != "null" ]]; then + export AAVE_QUOTE_PUSH_RECEIVER_MAINNET="$inferred_receiver" + fi fi fi SKIP_FLASH="${QUOTE_PUSH_KEEPER_SKIP_FLASH:-0}" SKIP_RECYCLE="${QUOTE_PUSH_KEEPER_SKIP_RECYCLE:-0}" +ALLOW_NET_NEGATIVE="${QUOTE_PUSH_KEEPER_ALLOW_NET_NEGATIVE:-0}" TOKEN="${QUOTE_PUSH_SURPLUS_TOKEN_MAINNET:-$DEFAULT_USDC_MAINNET}" RECEIVER="${AAVE_QUOTE_PUSH_RECEIVER_MAINNET:-}" RECEIVER_RESERVE_RAW="${QUOTE_PUSH_RECEIVER_RESERVE_RAW:-0}" @@ -216,12 +265,32 @@ OP_BUFFER_ETH="${QUOTE_PUSH_OPERATION_BUFFER_ETH:-0.0005}" NATIVE_TOKEN_PRICE="${QUOTE_PUSH_NATIVE_TOKEN_PRICE:-3200}" DEPLOYER="$(cast wallet address --private-key "$PRIVATE_KEY")" MANAGER="${QUOTE_PUSH_TREASURY_MANAGER_MAINNET:-}" +if [[ -n "$MANAGER" ]]; then + manager_receiver="$(cast call "$MANAGER" 'receiver()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null | awk '{print $1}' || true)" + if [[ -z "$manager_receiver" ]]; then + echo "[fail] could not resolve receiver() from manager $MANAGER" >&2 + exit 1 + fi + if [[ -z "$RECEIVER" ]]; then + RECEIVER="$manager_receiver" + export AAVE_QUOTE_PUSH_RECEIVER_MAINNET="$RECEIVER" + elif [[ "${manager_receiver,,}" != "${RECEIVER,,}" ]]; then + cat >&2 <}" echo "skip_flash=$SKIP_FLASH" echo "skip_recycle=$SKIP_RECYCLE" +echo "allow_net_negative=$ALLOW_NET_NEGATIVE" bash "${PROXMOX_ROOT}/scripts/verify/report-mainnet-aave-quote-push-surplus-accounting.sh" @@ -276,7 +345,42 @@ fi if [[ -n "$MANAGER" && "$SKIP_FLASH" != "1" && "$SKIP_RECYCLE" != "1" ]]; then export QUOTE_PUSH_TREASURY_HARVEST=1 export QUOTE_PUSH_TREASURY_GAS_HOLDBACK_TARGET_RAW="$KEEPER_GAS_SHORTFALL_QUOTE_RAW" - bash "${PROXMOX_ROOT}/scripts/deployment/run-mainnet-aave-quote-push-managed-cycle.sh" "--${MODE}" + set +e + managed_cycle_output="$( + bash "${PROXMOX_ROOT}/scripts/deployment/run-mainnet-aave-quote-push-managed-cycle.sh" --dry-run 2>&1 + )" + managed_cycle_status=$? + set -e + printf '%s\n' "$managed_cycle_output" + if [[ "$managed_cycle_status" -ne 0 ]]; then + exit "$managed_cycle_status" + fi + + managed_summary_line="$(printf '%s\n' "$managed_cycle_output" | awk '/MANAGED_CYCLE_SUMMARY / {line=$0} END {print line}')" + if [[ -z "$managed_summary_line" ]]; then + echo "[fail] managed cycle precheck did not emit MANAGED_CYCLE_SUMMARY" >&2 + exit 1 + fi + + parse_managed_cycle_summary "$managed_summary_line" "$NATIVE_TOKEN_PRICE" + + echo + echo "=== Managed cycle economics ===" + echo "modeled_harvested_raw=$KEEPER_MANAGED_HARVESTED_RAW human=$(to_human "$KEEPER_MANAGED_HARVESTED_RAW")" + echo "modeled_gas_distribution_raw=$KEEPER_MANAGED_GAS_DISTRIBUTION_RAW human=$(to_human "$KEEPER_MANAGED_GAS_DISTRIBUTION_RAW")" + echo "modeled_recycle_distribution_raw=$KEEPER_MANAGED_RECYCLE_DISTRIBUTION_RAW human=$(to_human "$KEEPER_MANAGED_RECYCLE_DISTRIBUTION_RAW")" + echo "modeled_estimated_eth_required=$KEEPER_MANAGED_ESTIMATED_ETH_REQUIRED" + echo "modeled_estimated_total_gas=$KEEPER_MANAGED_ESTIMATED_TOTAL_GAS" + echo "modeled_gas_cost_quote_raw=$KEEPER_MANAGED_GAS_COST_QUOTE_RAW human=$(to_human "$KEEPER_MANAGED_GAS_COST_QUOTE_RAW")" + echo "modeled_net_quote_raw=$KEEPER_MANAGED_NET_QUOTE_RAW human=$(to_human "$KEEPER_MANAGED_NET_QUOTE_RAW")" + + if (( KEEPER_MANAGED_NET_QUOTE_RAW <= 0 )) && [[ "$ALLOW_NET_NEGATIVE" != "1" ]]; then + echo "[stop] managed cycle is net negative after modeled gas; refusing to execute without QUOTE_PUSH_KEEPER_ALLOW_NET_NEGATIVE=1" + if [[ "$MODE" == "dry-run" ]]; then + exit 0 + fi + exit 1 + fi if [[ "$MODE" == "dry-run" ]]; then if [[ "${KEEPER_RECYCLE_RECIPIENT,,}" == "${DEPLOYER,,}" ]]; then @@ -285,6 +389,8 @@ if [[ -n "$MANAGER" && "$SKIP_FLASH" != "1" && "$SKIP_RECYCLE" != "1" ]]; then exit 0 fi + bash "${PROXMOX_ROOT}/scripts/deployment/run-mainnet-aave-quote-push-managed-cycle.sh" --apply + echo echo "=== Post-execution accounting ===" bash "${PROXMOX_ROOT}/scripts/verify/report-mainnet-aave-quote-push-surplus-accounting.sh" diff --git a/scripts/deployment/run-mainnet-aave-quote-push-managed-cycle.sh b/scripts/deployment/run-mainnet-aave-quote-push-managed-cycle.sh index 9f48a23a..479df463 100644 --- a/scripts/deployment/run-mainnet-aave-quote-push-managed-cycle.sh +++ b/scripts/deployment/run-mainnet-aave-quote-push-managed-cycle.sh @@ -44,6 +44,7 @@ _qp_mid_token="${UNWIND_TWO_HOP_MID_TOKEN-}" _qp_min_mid_out="${UNWIND_MIN_MID_OUT_RAW-}" _qp_min_out_pmm="${MIN_OUT_PMM-}" _qp_min_out_unwind="${MIN_OUT_UNWIND-}" +_qp_min_out_unwind_buffer="${MIN_OUT_UNWIND_BUFFER_RAW-}" _qp_fee_u24="${UNWIND_V3_FEE_U24-}" _qp_dodo_pool="${UNWIND_DODO_POOL-}" _qp_v3_path="${UNWIND_V3_PATH_HEX-}" @@ -73,6 +74,7 @@ source "${SMOM}/scripts/load-env.sh" >/dev/null 2>&1 || true [[ -n "$_qp_min_mid_out" ]] && export UNWIND_MIN_MID_OUT_RAW="$_qp_min_mid_out" [[ -n "$_qp_min_out_pmm" ]] && export MIN_OUT_PMM="$_qp_min_out_pmm" [[ -n "$_qp_min_out_unwind" ]] && export MIN_OUT_UNWIND="$_qp_min_out_unwind" +[[ -n "$_qp_min_out_unwind_buffer" ]] && export MIN_OUT_UNWIND_BUFFER_RAW="$_qp_min_out_unwind_buffer" [[ -n "$_qp_fee_u24" ]] && export UNWIND_V3_FEE_U24="$_qp_fee_u24" [[ -n "$_qp_dodo_pool" ]] && export UNWIND_DODO_POOL="$_qp_dodo_pool" [[ -n "$_qp_v3_path" ]] && export UNWIND_V3_PATH_HEX="$_qp_v3_path" @@ -83,9 +85,17 @@ source "${SMOM}/scripts/load-env.sh" >/dev/null 2>&1 || true unset _qp_private_key _qp_rpc _qp_receiver _qp_manager _qp_unwinder _qp_amount _qp_unwind_type _qp_unwind_mode unset _qp_pool _qp_integration _qp_pool_a _qp_pool_b _qp_mid_token _qp_min_mid_out _qp_min_out_pmm -unset _qp_min_out_unwind _qp_fee_u24 _qp_dodo_pool _qp_v3_path _qp_intermediate_token _qp_min_intermediate_out +unset _qp_min_out_unwind _qp_min_out_unwind_buffer _qp_fee_u24 _qp_dodo_pool _qp_v3_path +unset _qp_intermediate_token _qp_min_intermediate_out unset _qp_treasury_harvest _qp_treasury_holdback +require_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "[fail] missing required command: $1" >&2 + exit 1 + } +} + BROADCAST=() if (($# == 0)); then : @@ -102,6 +112,10 @@ else done fi +require_cmd cast +require_cmd forge +require_cmd mktemp + require() { local n="$1" if [[ -z "${!n:-}" ]]; then @@ -158,10 +172,26 @@ pick_default_unwinder() { return 0 fi + addr="$(pick_latest_create_address "DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder.s.sol" "TwoHopDodoToUniswapV3MultiHopExternalUnwinder" || true)" + if [[ -n "$addr" && "$addr" != "null" ]]; then + PICK_DEFAULT_UNWINDER_ADDR="$addr" + PICK_DEFAULT_UNWINDER_MODE="6" + return 0 + fi + return 1 } UNW="${QUOTE_PUSH_UNWINDER_TYPE:-}" +if [[ -z "${AAVE_QUOTE_PUSH_RECEIVER_MAINNET:-}" ]]; then + if [[ -n "${QUOTE_PUSH_TREASURY_MANAGER_MAINNET:-}" && -n "${ETHEREUM_MAINNET_RPC:-}" ]]; then + manager_receiver="$(cast call "$QUOTE_PUSH_TREASURY_MANAGER_MAINNET" 'receiver()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null | awk '{print $1}' || true)" + if [[ -n "$manager_receiver" && "$manager_receiver" != "0x0000000000000000000000000000000000000000" ]]; then + export AAVE_QUOTE_PUSH_RECEIVER_MAINNET="$manager_receiver" + fi + fi +fi + if [[ -z "${AAVE_QUOTE_PUSH_RECEIVER_MAINNET:-}" ]]; then inferred_receiver="$(pick_latest_create_address "DeployAaveQuotePushFlashReceiver.s.sol" "AaveQuotePushFlashReceiver" || true)" export AAVE_QUOTE_PUSH_RECEIVER_MAINNET="${inferred_receiver:-$DEFAULT_AAVE_QUOTE_PUSH_RECEIVER_MAINNET}" @@ -203,8 +233,13 @@ if [[ -z "${QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET:-}" && -n "$UNW" ]]; then unwind_contract="DODOToUniswapV3MultiHopExternalUnwinder" export UNWIND_MODE="${UNWIND_MODE:-5}" ;; + two_hop_dodo_univ3) + unwind_script="DeployTwoHopDodoToUniswapV3MultiHopExternalUnwinder.s.sol" + unwind_contract="TwoHopDodoToUniswapV3MultiHopExternalUnwinder" + export UNWIND_MODE="${UNWIND_MODE:-6}" + ;; *) - echo "[fail] QUOTE_PUSH_UNWINDER_TYPE must be univ3, dodo, two_hop_dodo, or dodo_univ3 when set" >&2 + echo "[fail] QUOTE_PUSH_UNWINDER_TYPE must be univ3, dodo, two_hop_dodo, dodo_univ3, or two_hop_dodo_univ3 when set" >&2 exit 1 ;; esac @@ -233,6 +268,14 @@ elif [[ "${UNWIND_MODE:-}" == "5" ]]; then export UNWIND_DODO_POOL="${UNWIND_DODO_POOL:-0xCC0fd27A40775c9AfcD2BBd3f7c902b0192c247A}" export UNWIND_INTERMEDIATE_TOKEN="${UNWIND_INTERMEDIATE_TOKEN:-0xdAC17F958D2ee523a2206206994597C13D831ec7}" export UNWIND_MIN_INTERMEDIATE_OUT_RAW="${UNWIND_MIN_INTERMEDIATE_OUT_RAW:-1}" +elif [[ "${UNWIND_MODE:-}" == "6" ]]; then + export UNWIND_TWO_HOP_POOL_A="${UNWIND_TWO_HOP_POOL_A:-0xe944b7Cb012A0820c07f54D51e92f0e1C74168DB}" + export UNWIND_TWO_HOP_POOL_B="${UNWIND_TWO_HOP_POOL_B:-0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC}" + export UNWIND_TWO_HOP_MID_TOKEN="${UNWIND_TWO_HOP_MID_TOKEN:-0xaF5017d0163ecb99d9B5D94e3b4D7b09Af44D8AE}" + export UNWIND_INTERMEDIATE_TOKEN="${UNWIND_INTERMEDIATE_TOKEN:-0xdAC17F958D2ee523a2206206994597C13D831ec7}" + export UNWIND_MIN_MID_OUT_RAW="${UNWIND_MIN_MID_OUT_RAW:-1}" + export UNWIND_MIN_INTERMEDIATE_OUT_RAW="${UNWIND_MIN_INTERMEDIATE_OUT_RAW:-1}" + export MIN_OUT_UNWIND_BUFFER_RAW="${MIN_OUT_UNWIND_BUFFER_RAW:-0}" fi require ETHEREUM_MAINNET_RPC @@ -243,6 +286,21 @@ require QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET require DODO_PMM_INTEGRATION_MAINNET require FLASH_QUOTE_AMOUNT_RAW +manager_receiver="$(cast call "$QUOTE_PUSH_TREASURY_MANAGER_MAINNET" 'receiver()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null | awk '{print $1}' || true)" +if [[ -z "$manager_receiver" ]]; then + echo "[fail] could not resolve receiver() from manager $QUOTE_PUSH_TREASURY_MANAGER_MAINNET" >&2 + exit 1 +fi +if [[ "${manager_receiver,,}" != "${AAVE_QUOTE_PUSH_RECEIVER_MAINNET,,}" ]]; then + cat >&2 <&2 + echo "[fail] UNWIND_MODE must be 0, 1, 2, 4, 5, or 6" >&2 exit 1 fi @@ -269,10 +333,31 @@ echo "unwinder=$QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET" echo "flash_quote_amount_raw=$FLASH_QUOTE_AMOUNT_RAW unwind_mode=$UM" echo "gas_holdback_target_raw=${QUOTE_PUSH_TREASURY_GAS_HOLDBACK_TARGET_RAW:-0}" +tmp_output="$(mktemp)" +set +e ( cd "$SMOM" forge script script/flash/RunManagedMainnetAaveCwusdcUsdcQuotePushCycle.s.sol:RunManagedMainnetAaveCwusdcUsdcQuotePushCycle \ --rpc-url "$ETHEREUM_MAINNET_RPC" \ "${BROADCAST[@]}" \ -vvvv -) +) 2>&1 | tee "$tmp_output" +status=${PIPESTATUS[0]} +set -e + +if [[ "$status" -ne 0 ]]; then + rm -f "$tmp_output" + exit "$status" +fi + +managed_harvested_raw="$(sed -nE 's/.*managedCycleHarvestedRaw", ([0-9]+).*/\1/p' "$tmp_output" | tail -n1)" +managed_gas_distribution_raw="$(sed -nE 's/.*managedCycleGasDistributionRaw", ([0-9]+).*/\1/p' "$tmp_output" | tail -n1)" +managed_recycle_distribution_raw="$(sed -nE 's/.*managedCycleRecycleDistributionRaw", ([0-9]+).*/\1/p' "$tmp_output" | tail -n1)" +estimated_eth_required="$(sed -nE 's/Estimated amount required: ([0-9.]+) ETH/\1/p' "$tmp_output" | tail -n1)" +estimated_total_gas="$(sed -nE 's/Estimated total gas used for script: ([0-9]+)/\1/p' "$tmp_output" | tail -n1)" + +if [[ -n "$managed_recycle_distribution_raw" || -n "$estimated_eth_required" || -n "$estimated_total_gas" ]]; then + echo "MANAGED_CYCLE_SUMMARY harvested_raw=${managed_harvested_raw:-0} gas_distribution_raw=${managed_gas_distribution_raw:-0} recycle_distribution_raw=${managed_recycle_distribution_raw:-0} estimated_eth_required=${estimated_eth_required:-0} estimated_total_gas=${estimated_total_gas:-0}" +fi + +rm -f "$tmp_output"