Files
proxmox/scripts/verify/verify-dodo-v3-chain138-blockscout.sh
defiQUG dbd517b279 Sync workspace: config, docs, scripts, CI, operator rules, and submodule pointers.
- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains
- Omit embedded publish git dirs and empty placeholders from index

Made-with: Cursor
2026-04-12 06:12:20 -07:00

387 lines
13 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# Verify the Chain 138 DODO v3 / D3MM pilot deploy set on Blockscout using
# the existing Forge verification proxy bridge.
#
# Usage:
# bash scripts/verify/verify-dodo-v3-chain138-blockscout.sh
# bash scripts/verify/verify-dodo-v3-chain138-blockscout.sh --status-only
# bash scripts/verify/verify-dodo-v3-chain138-blockscout.sh --only D3Oracle,D3Vault
#
# Notes:
# - This verifies the core DODO v3 pilot control-plane contracts by default.
# - Support/template/mock deployments can still be verified by passing `--only`.
# - The canonical D3MM pilot pool itself is clone-based and is tracked through
# the verified factory set plus the runtime health check in
# `check-dodo-v3-chain138.sh`.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
if [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]]; then
# shellcheck source=/dev/null
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh"
fi
command -v forge >/dev/null 2>&1 || { echo "ERROR: forge not found"; exit 1; }
command -v node >/dev/null 2>&1 || { echo "ERROR: node not found"; exit 1; }
command -v cast >/dev/null 2>&1 || { echo "ERROR: cast not found"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "ERROR: jq not found"; exit 1; }
command -v curl >/dev/null 2>&1 || { echo "ERROR: curl not found"; exit 1; }
DODO_V3_DIR="${DODO_V3_DIR:-/home/intlc/projects/dodo-v3}"
DEPLOYMENTS_DIR="${DODO_V3_DIR}/deployments/chain138"
[[ -d "${DODO_V3_DIR}" ]] || { echo "ERROR: dodo-v3 not found at ${DODO_V3_DIR}"; exit 1; }
[[ -d "${DEPLOYMENTS_DIR}" ]] || { echo "ERROR: deployments dir not found at ${DEPLOYMENTS_DIR}"; exit 1; }
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC_URL:-${CHAIN138_RPC:-http://192.168.11.211:8545}}}"
BLOCKSCOUT_URL="${CHAIN138_BLOCKSCOUT_INTERNAL_URL:-http://${IP_BLOCKSCOUT:-192.168.11.140}:4000}"
BLOCKSCOUT_API_BASE="${CHAIN138_BLOCKSCOUT_API_BASE:-${BLOCKSCOUT_URL}/api/v2}"
BLOCKSCOUT_PUBLIC_API_BASE="${CHAIN138_BLOCKSCOUT_PUBLIC_API_BASE:-https://explorer.d-bis.org/api/v2}"
VERIFIER_PORT="${FORGE_VERIFIER_PROXY_PORT:-3080}"
FORGE_VERIFIER_URL="${FORGE_VERIFIER_URL:-http://127.0.0.1:${VERIFIER_PORT}/api}"
WAIT_ATTEMPTS="${DODO_V3_VERIFY_WAIT_ATTEMPTS:-18}"
WAIT_SECONDS="${DODO_V3_VERIFY_WAIT_SECONDS:-5}"
ONLY_LIST=""
SKIP_LIST=""
STATUS_ONLY=0
NO_WAIT=0
PROXY_PID=""
while [[ $# -gt 0 ]]; do
case "$1" in
--only) ONLY_LIST="${2:-}"; shift 2 ;;
--skip) SKIP_LIST="${2:-}"; shift 2 ;;
--status-only) STATUS_ONLY=1; shift ;;
--no-wait) NO_WAIT=1; shift ;;
*)
echo "Unknown argument: $1" >&2
exit 1
;;
esac
done
cleanup_proxy() {
[[ -n "${PROXY_PID:-}" ]] && kill "${PROXY_PID}" 2>/dev/null || true
}
trap cleanup_proxy EXIT
log() {
printf '%s\n' "$*"
}
ok() {
printf '[ok] %s\n' "$*"
}
warn() {
printf '[warn] %s\n' "$*" >&2
}
fail() {
printf '[fail] %s\n' "$*" >&2
exit 1
}
should_handle() {
local name="$1"
[[ -n "${ONLY_LIST}" ]] && [[ ",${ONLY_LIST}," != *",${name},"* ]] && return 1
[[ -n "${SKIP_LIST}" ]] && [[ ",${SKIP_LIST}," = *",${name},"* ]] && return 1
return 0
}
deployment_json() {
local file="$1"
printf '%s/%s.json' "${DEPLOYMENTS_DIR}" "${file}"
}
deployment_address() {
jq -r '.address' "$(deployment_json "$1")"
}
deployment_arg() {
local file="$1"
local index="$2"
jq -r ".args[${index}]" "$(deployment_json "${file}")"
}
deployment_array_literal() {
local file="$1"
local index="$2"
jq -r ".args[${index}] | \"[\" + (join(\",\")) + \"]\"" "$(deployment_json "${file}")"
}
deployment_solc_input_path() {
local file="$1"
local hash
hash="$(jq -r '.solcInputHash' "$(deployment_json "${file}")")"
printf '%s/solcInputs/%s.json' "${DEPLOYMENTS_DIR}" "${hash}"
}
has_contract_bytecode() {
local addr="$1"
local code
code="$(cast code "${addr}" --rpc-url "${RPC_URL}" 2>/dev/null | tr -d '\n\r \t' | tr '[:upper:]' '[:lower:]')" || true
[[ -n "${code}" && "${code}" != "0x" && "${code}" != "0x0" ]]
}
proxy_listening() {
if command -v nc >/dev/null 2>&1; then
nc -z -w 2 127.0.0.1 "${VERIFIER_PORT}" 2>/dev/null
else
timeout 2 bash -c "echo >/dev/tcp/127.0.0.1/${VERIFIER_PORT}" 2>/dev/null
fi
}
start_proxy_if_needed() {
if proxy_listening; then
ok "Forge verification proxy already listening on ${VERIFIER_PORT}."
return 0
fi
log "Starting forge verification proxy on ${VERIFIER_PORT} -> ${BLOCKSCOUT_URL}"
PORT="${VERIFIER_PORT}" BLOCKSCOUT_URL="${BLOCKSCOUT_URL}" node "${PROJECT_ROOT}/forge-verification-proxy/server.js" >/tmp/dodo-v3-blockscout-proxy.log 2>&1 &
PROXY_PID=$!
sleep 2
proxy_listening || fail "Forge verification proxy failed to start. See /tmp/dodo-v3-blockscout-proxy.log"
}
verification_status_json() {
local addr="$1"
local raw
local base
for base in "${BLOCKSCOUT_API_BASE}" "${BLOCKSCOUT_PUBLIC_API_BASE}"; do
for _ in 1 2 3; do
raw="$(curl --max-time 15 -fsS "${base}/smart-contracts/${addr}" 2>/dev/null || true)"
if [[ -n "${raw}" ]] && jq -e 'type == "object"' >/dev/null 2>&1 <<<"${raw}"; then
printf '%s' "${raw}"
return 0
fi
sleep 2
done
done
return 1
}
has_bytecode_surface_only() {
local addr="$1"
local json
json="$(verification_status_json "${addr}")" || return 1
jq -e '.creation_bytecode and .deployed_bytecode and ((.name // "") == "") and ((.compiler_version // "") == "")' >/dev/null 2>&1 <<<"${json}"
}
is_verified() {
local addr="$1"
local expected_name="$2"
local json name compiler
json="$(verification_status_json "${addr}")" || return 1
name="$(jq -r '.name // empty' <<<"${json}")"
compiler="$(jq -r '.compiler_version // empty' <<<"${json}")"
[[ -n "${name}" && -n "${compiler}" && "${name}" == "${expected_name}" ]]
}
wait_for_verification() {
local label="$1"
local addr="$2"
local expected_name="$3"
local attempt json name compiler
for (( attempt=1; attempt<=WAIT_ATTEMPTS; attempt++ )); do
json="$(verification_status_json "${addr}")" || json=""
name="$(jq -r '.name // empty' <<<"${json}" 2>/dev/null || true)"
compiler="$(jq -r '.compiler_version // empty' <<<"${json}" 2>/dev/null || true)"
if [[ -n "${name}" && -n "${compiler}" && "${name}" == "${expected_name}" ]]; then
ok "${label} verified on Blockscout as ${name} (${compiler})."
return 0
fi
sleep "${WAIT_SECONDS}"
done
return 1
}
contract_path() {
case "$1" in
CloneFactory) printf '%s' 'contracts/DODOV3MM/lib/CloneFactory.sol:CloneFactory' ;;
D3FeeRateModel) printf '%s' 'contracts/mock/D3FeeRateModel.sol:D3FeeRateModel' ;;
D3MakerTemplate) printf '%s' 'contracts/DODOV3MM/D3Pool/D3Maker.sol:D3Maker' ;;
D3MMFactory) printf '%s' 'contracts/DODOV3MM/periphery/D3MMFactory.sol:D3MMFactory' ;;
D3MMLiquidationRouter) printf '%s' 'contracts/DODOV3MM/periphery/D3MMLiquidationRouter.sol:D3MMLiquidationRouter' ;;
D3MMTemplate) printf '%s' 'contracts/DODOV3MM/D3Pool/D3MM.sol:D3MM' ;;
D3Oracle) printf '%s' 'contracts/DODOV3MM/periphery/D3Oracle.sol:D3Oracle' ;;
D3PoolQuota) printf '%s' 'contracts/DODOV3MM/D3Vault/periphery/D3PoolQuota.sol:D3PoolQuota' ;;
D3Proxy) printf '%s' 'contracts/DODOV3MM/periphery/D3Proxy.sol:D3Proxy' ;;
D3RateManager) printf '%s' 'contracts/DODOV3MM/D3Vault/periphery/D3RateManager.sol:D3RateManager' ;;
D3TokenTemplate) printf '%s' 'contracts/DODOV3MM/D3Vault/periphery/D3Token.sol:D3Token' ;;
D3Vault) printf '%s' 'contracts/DODOV3MM/D3Vault/D3Vault.sol:D3Vault' ;;
DODOApprove) printf '%s' 'contracts/mock/DODOApprove.sol:DODOApprove' ;;
DODOApproveProxy) printf '%s' 'contracts/mock/DODOApproveProxy.sol:DODOApproveProxy' ;;
MockChainlinkPriceFeed) printf '%s' 'contracts/mock/MockChainlinkPriceFeed.sol:MockChainlinkPriceFeed' ;;
*) return 1 ;;
esac
}
constructor_args() {
local deployment_file="$1"
local label="$2"
case "${label}" in
D3MMFactory)
cast abi-encode \
"constructor(address,address[],address[],address,address,address,address,address)" \
"$(deployment_arg "${deployment_file}" 0)" \
"$(deployment_array_literal "${deployment_file}" 1)" \
"$(deployment_array_literal "${deployment_file}" 2)" \
"$(deployment_arg "${deployment_file}" 3)" \
"$(deployment_arg "${deployment_file}" 4)" \
"$(deployment_arg "${deployment_file}" 5)" \
"$(deployment_arg "${deployment_file}" 6)" \
"$(deployment_arg "${deployment_file}" 7)"
;;
D3Proxy)
cast abi-encode \
"constructor(address,address,address)" \
"$(deployment_arg "${deployment_file}" 0)" \
"$(deployment_arg "${deployment_file}" 1)" \
"$(deployment_arg "${deployment_file}" 2)"
;;
D3MMLiquidationRouter|DODOApproveProxy)
cast abi-encode \
"constructor(address)" \
"$(deployment_arg "${deployment_file}" 0)"
;;
MockUSDTUsd|MockUSDCUsd|MockcUSDTUsd|MockcUSDCUsd)
cast abi-encode \
"constructor(string,uint8)" \
"$(deployment_arg "${deployment_file}" 0)" \
"$(deployment_arg "${deployment_file}" 1)"
;;
*)
printf '%s' ""
;;
esac
}
submit_v2_standard_input() {
local label="$1"
local deployment_file="$2"
local addr="$3"
local path="$4"
local constructor="$5"
local input compiler_version license_type response message
input="$(deployment_solc_input_path "${deployment_file}")"
[[ -f "${input}" ]] || fail "${label}: missing standard-input artifact ${input}"
compiler_version="v0.8.16+commit.07a7930e"
license_type="busl_1_1"
response="$(
curl --max-time 30 -fsS -X POST \
-F "compiler_version=${compiler_version}" \
-F "contract_name=${path}" \
-F "autodetect_constructor_args=false" \
-F "constructor_args=${constructor}" \
-F "optimization_runs=200" \
-F "is_optimization_enabled=true" \
-F "license_type=${license_type}" \
-F "files[0]=@${input};type=application/json" \
"${BLOCKSCOUT_URL}/api/v2/smart-contracts/${addr}/verification/via/standard-input"
)" || fail "${label}: Blockscout standard-input submission failed."
message="$(jq -r '.message // empty' <<<"${response}")"
if [[ "${message}" == "Smart-contract verification started" ]]; then
ok "${label} standard-input verification submission accepted."
return 0
fi
warn "${label} standard-input verification returned: ${response}"
return 1
}
verify_one() {
local label="$1"
local deployment_file="$2"
local expected_name="$3"
local path="$4"
local addr constructor
should_handle "${label}" || return 0
addr="$(deployment_address "${deployment_file}")"
[[ "${addr}" != "null" && -n "${addr}" ]] || fail "${label}: missing address in ${deployment_file}.json"
has_contract_bytecode "${addr}" || fail "${label}: no bytecode at ${addr}"
if is_verified "${addr}" "${expected_name}"; then
ok "${label} already verified on Blockscout."
return 0
fi
if [[ "${STATUS_ONLY}" == "1" ]]; then
if [[ "${label}" == "D3MMFactory" || "${label}" == "D3Proxy" ]] && has_bytecode_surface_only "${addr}"; then
warn "${label} currently exposes only bytecode metadata on Blockscout."
return 0
fi
warn "${label} is not currently verified on Blockscout."
return 1
fi
start_proxy_if_needed
constructor="$(constructor_args "${deployment_file}" "${label}")"
log "Submitting Blockscout verification for ${label} (${addr})"
if [[ "${label}" == "D3MMFactory" || "${label}" == "D3Proxy" ]]; then
submit_v2_standard_input "${label}" "${deployment_file}" "${addr}" "${path}" "${constructor}" || true
else
(
cd "${DODO_V3_DIR}"
cmd=(
forge verify-contract
"${addr}"
"${path}"
--chain-id 138
--verifier blockscout
--verifier-url "${FORGE_VERIFIER_URL}"
--rpc-url "${RPC_URL}"
--flatten
--skip-is-verified-check
)
if [[ -n "${constructor}" ]]; then
cmd+=(--constructor-args "${constructor}")
fi
"${cmd[@]}" || true
)
fi
if [[ "${NO_WAIT}" == "1" ]]; then
ok "${label} verification submitted."
return 0
fi
if wait_for_verification "${label}" "${addr}" "${expected_name}"; then
return 0
fi
if [[ "${label}" == "D3MMFactory" || "${label}" == "D3Proxy" ]] && has_bytecode_surface_only "${addr}"; then
warn "${label} submission was accepted, but Blockscout still exposes only bytecode metadata for this address."
return 0
fi
fail "${label} did not show as verified on Blockscout after ${WAIT_ATTEMPTS} checks."
}
log "Chain 138 DODO v3 Blockscout verification"
log "DODO v3 dir: ${DODO_V3_DIR}"
log "RPC: ${RPC_URL}"
log "Explorer API: ${BLOCKSCOUT_API_BASE}"
log
verify_one D3Oracle D3Oracle D3Oracle "$(contract_path D3Oracle)"
verify_one D3Vault D3Vault D3Vault "$(contract_path D3Vault)"
verify_one DODOApprove DODOApprove DODOApprove "$(contract_path DODOApprove)"
verify_one DODOApproveProxy DODOApproveProxy DODOApproveProxy "$(contract_path DODOApproveProxy)"
verify_one D3MMFactory D3MMFactory D3MMFactory "$(contract_path D3MMFactory)"
verify_one D3Proxy D3Proxy D3Proxy "$(contract_path D3Proxy)"
log
ok "Chain 138 DODO v3 core pilot set is verified on Blockscout."
log "Note: the canonical D3MM pilot pool is clone-based and remains tracked through the verified factory set plus the runtime health check."