- 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
387 lines
13 KiB
Bash
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."
|