diff --git a/docs/04-configuration/MEV_CONTROL_COMPLETION_PUNCHLIST.md b/docs/04-configuration/MEV_CONTROL_COMPLETION_PUNCHLIST.md index f3dae295..f2c88f2d 100644 --- a/docs/04-configuration/MEV_CONTROL_COMPLETION_PUNCHLIST.md +++ b/docs/04-configuration/MEV_CONTROL_COMPLETION_PUNCHLIST.md @@ -45,6 +45,7 @@ Status labels: | Real bundle signing | `partial` | Code path exists and can sign `executeArbitrage(...)` transactions, but live deployment remains blocked by missing signer key, executor contract, flash-loan provider, and V2 router config | | Inclusion detection | `partial` | Receipt polling path exists, but real inclusion truth still depends on real signed submission and relay acceptance | | Profit realization accuracy | `partial` | Analytics work, but realized PnL still depends on live submission and real inclusion outcomes | +| Executor deployment capture | `live` | `deploy-mev-execution-contracts.sh` can deploy and capture non-secret execution addresses into a JSON artifact | ## Market / Search Coverage diff --git a/docs/04-configuration/MEV_EXECUTION_VALUE_SOURCES_AND_READINESS.md b/docs/04-configuration/MEV_EXECUTION_VALUE_SOURCES_AND_READINESS.md index da210703..617dca44 100644 --- a/docs/04-configuration/MEV_EXECUTION_VALUE_SOURCES_AND_READINESS.md +++ b/docs/04-configuration/MEV_EXECUTION_VALUE_SOURCES_AND_READINESS.md @@ -90,6 +90,28 @@ They should not be filled by assumption if the deployment target is expected to 3. Actual deployment output or operator-selected venue address for `flash_loan_provider`. 4. Canonical chain venue inventory for router addresses, then validate through the readiness script and live `/api/safety/signer`. +## Auditable deployment capture + +The repo now includes a deployment helper that uses the existing Foundry script and captures the resulting addresses into a JSON artifact instead of relying on manual copy/paste: + +```bash +bash scripts/deployment/deploy-mev-execution-contracts.sh --dry-run \ + --rpc-url "$RPC_URL" \ + --flash-loan-provider 0x... +``` + +When you are ready to broadcast: + +```bash +PRIVATE_KEY=0x... \ +bash scripts/deployment/deploy-mev-execution-contracts.sh \ + --rpc-url "$RPC_URL" \ + --flash-loan-provider 0x... \ + --treasury 0x... +``` + +This produces a JSON artifact under `reports/status/` and prints the exact non-secret config values to update next. + ## Commit policy Safe to commit: diff --git a/scripts/deployment/deploy-mev-execution-contracts.sh b/scripts/deployment/deploy-mev-execution-contracts.sh new file mode 100755 index 00000000..66d41ae6 --- /dev/null +++ b/scripts/deployment/deploy-mev-execution-contracts.sh @@ -0,0 +1,217 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +CONTRACTS_DIR="$ROOT/MEV_Bot/mev-platform/contracts" +CONFIG_PATH_DEFAULT="$ROOT/MEV_Bot/mev-platform/config.toml" +OUTPUT_DEFAULT="$ROOT/reports/status/mev_execution_deploy_$(date +%Y%m%d_%H%M%S).json" + +RPC_URL="${RPC_URL:-}" +PRIVATE_KEY="${PRIVATE_KEY:-}" +FLASH_LOAN_PROVIDER="${FLASH_LOAN_PROVIDER:-}" +TREASURY="${TREASURY:-}" +CONFIG_PATH="${MEV_CONFIG_PATH:-$CONFIG_PATH_DEFAULT}" +OUTPUT_PATH="${MEV_EXECUTION_DEPLOY_OUTPUT:-$OUTPUT_DEFAULT}" +DRY_RUN=0 + +usage() { + cat <<'EOF' +Usage: deploy-mev-execution-contracts.sh [options] + +Deploys the MEV ArbitrageExecutor and UniswapV2Adapter using the Foundry script +already present in the MEV contracts workspace, then captures the deployed +addresses into a JSON artifact and prints the exact config values that should be +updated afterward. + +Required env or options: + RPC_URL Target chain RPC URL + PRIVATE_KEY Deployer private key + FLASH_LOAN_PROVIDER Non-zero provider address compatible with the executor + +Optional env or options: + TREASURY Treasury address; defaults to deployer in Foundry script + MEV_CONFIG_PATH Config file to inspect for current values + MEV_EXECUTION_DEPLOY_OUTPUT Output JSON path + +Options: + --rpc-url URL + --private-key KEY + --flash-loan-provider ADDRESS + --treasury ADDRESS + --config PATH + --output PATH + --dry-run + -h, --help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --rpc-url) + RPC_URL="$2" + shift 2 + ;; + --private-key) + PRIVATE_KEY="$2" + shift 2 + ;; + --flash-loan-provider) + FLASH_LOAN_PROVIDER="$2" + shift 2 + ;; + --treasury) + TREASURY="$2" + shift 2 + ;; + --config) + CONFIG_PATH="$2" + shift 2 + ;; + --output) + OUTPUT_PATH="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "Required command missing: $1" >&2 + exit 2 + } +} + +require_cmd forge +require_cmd jq +require_cmd python3 + +if [[ -z "$RPC_URL" ]]; then + echo "RPC_URL is required" >&2 + exit 2 +fi + +if [[ -z "$PRIVATE_KEY" && "$DRY_RUN" -eq 0 ]]; then + echo "PRIVATE_KEY is required unless --dry-run is used" >&2 + exit 2 +fi + +if [[ -z "$FLASH_LOAN_PROVIDER" ]]; then + echo "FLASH_LOAN_PROVIDER is required and must be non-zero" >&2 + exit 2 +fi + +if [[ ! -d "$CONTRACTS_DIR" ]]; then + echo "Contracts directory not found: $CONTRACTS_DIR" >&2 + exit 2 +fi + +mkdir -p "$(dirname "$OUTPUT_PATH")" + +CHAIN_ID="$(cast chain-id --rpc-url "$RPC_URL")" +BROADCAST_PATH="$CONTRACTS_DIR/broadcast/Deploy.s.sol/$CHAIN_ID/run-latest.json" + +cat <&2 + exit 1 +fi + +python3 - "$BROADCAST_PATH" "$OUTPUT_PATH" "$CONFIG_PATH" "$FLASH_LOAN_PROVIDER" "$TREASURY" "$CHAIN_ID" <<'PY' +import json +import re +import sys +from pathlib import Path + +broadcast_path = Path(sys.argv[1]) +output_path = Path(sys.argv[2]) +config_path = Path(sys.argv[3]) +flash_loan_provider = sys.argv[4] +treasury = sys.argv[5] or None +chain_id = int(sys.argv[6]) + +data = json.loads(broadcast_path.read_text()) +transactions = data.get("transactions", []) + +executor = None +adapter = None +for tx in transactions: + contract_name = tx.get("contractName") + address = tx.get("contractAddress") + if contract_name == "ArbitrageExecutor" and address: + executor = address + elif contract_name == "UniswapV2Adapter" and address: + adapter = address + +if not executor or not adapter: + raise SystemExit("Could not extract deployed contract addresses from broadcast artifact") + +artifact = { + "chain_id": chain_id, + "broadcast_artifact": str(broadcast_path), + "config_path": str(config_path), + "executor_contract": executor, + "uniswap_v2_adapter": adapter, + "flash_loan_provider": flash_loan_provider, + "treasury": treasury, +} +output_path.write_text(json.dumps(artifact, indent=2) + "\n") + +print("") +print("captured deployment artifact:") +print(output_path) +print("") +print(json.dumps(artifact, indent=2)) +print("") +print("next config values:") +print(f'chains.{chain_id}.execution.executor_contract = "{executor}"') +print(f'chains.{chain_id}.execution.flash_loan_provider = "{flash_loan_provider}"') +print("") +print("next operator checks:") +print(f"- verify code exists at {executor}") +print(f"- confirm the signer EOA is the executor owner") +print("- set router addresses for every V2-style dex used by execution") +print("- keep MEV_SUBMIT_DISABLED=1 until signer/config readiness is green") +PY