Files
proxmox/scripts/verify/check-dodo-v3-chain138.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

340 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Verify the private Chain 138 DODO v3 / D3MM rollout.
#
# Checks:
# 1. Canonical D3MM pool returns the expected version string.
# 2. D3Vault still recognizes the canonical pool.
# 3. D3Oracle has a non-zero, whitelisted WETH10 source.
# 4. A quote probe for WETH10 -> USDT returns a healthy amount.
# 5. Core DODO v3 pilot contracts remain source-verified on Blockscout.
#
# Optional env overrides:
# RPC_URL_138
# CHAIN138_D3_ORACLE_ADDRESS
# CHAIN138_D3_VAULT_ADDRESS
# CHAIN138_D3_MM_FACTORY_ADDRESS
# CHAIN138_D3_PROXY_ADDRESS
# CHAIN138_D3_DODO_APPROVE_ADDRESS
# CHAIN138_D3_DODO_APPROVE_PROXY_ADDRESS
# CHAIN138_D3_MM_ADDRESS
# CHAIN138_D3_WETH10_ADDRESS
# CHAIN138_D3_WETH_USD_FEED
# CHAIN138_D3_BOOTSTRAP_WETH_USD_MOCK
# CHAIN138_D3_USDT_ADDRESS
# CHAIN138_D3_USDC_ADDRESS
# CHAIN138_D3_CUSDT_ADDRESS
# CHAIN138_D3_CUSDC_ADDRESS
# CHAIN138_D3_USDT_USD_FEED
# CHAIN138_D3_USDC_USD_FEED
# CHAIN138_D3_CUSDT_USD_FEED
# CHAIN138_D3_CUSDC_USD_FEED
# CHAIN138_D3_ALLOW_BOOTSTRAP_STABLE_MOCKS=1 to downgrade old stable-feed addresses to warnings
# CHAIN138_D3_PROBE_SELL_AMOUNT
# CHAIN138_D3_MIN_QUOTE_OUT
# CHAIN138_BLOCKSCOUT_API_BASE
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
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC_URL:-${CHAIN138_RPC:-http://192.168.11.211:8545}}}"
D3_ORACLE="${CHAIN138_D3_ORACLE_ADDRESS:-0xD7459aEa8bB53C83a1e90262777D730539A326F0}"
D3_VAULT="${CHAIN138_D3_VAULT_ADDRESS:-0x42b6867260Fb9eE6d09B7E0233A1fAD65D0133D1}"
D3_FACTORY="${CHAIN138_D3_MM_FACTORY_ADDRESS:-0x78470C7d2925B6738544E2DD4FE7c07CcA21AC31}"
D3_PROXY="${CHAIN138_D3_PROXY_ADDRESS:-0xc9a11abB7C63d88546Be24D58a6d95e3762cB843}"
DODO_APPROVE="${CHAIN138_D3_DODO_APPROVE_ADDRESS:-0xbF8D5CB7E8F333CA686a27374Ae06F5dfd772E9E}"
DODO_APPROVE_PROXY="${CHAIN138_D3_DODO_APPROVE_PROXY_ADDRESS:-0x08d764c03C42635d8ef9046752b5694243E21Fe9}"
D3MM="${CHAIN138_D3_MM_ADDRESS:-0x6550A3a59070061a262a893A1D6F3F490afFDBDA}"
WETH10="${CHAIN138_D3_WETH10_ADDRESS:-0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F}"
WETH_USD_FEED="${CHAIN138_D3_WETH_USD_FEED:-0x99b3511a2d315a497c8112c1fdd8d508d4b1e506}"
BOOTSTRAP_WETH_MOCK="${CHAIN138_D3_BOOTSTRAP_WETH_USD_MOCK:-0xf1bbBc7FCBB96C6a143C752d4DAEFA34e192EE9c}"
USDT="${CHAIN138_D3_USDT_ADDRESS:-0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1}"
USDC="${CHAIN138_D3_USDC_ADDRESS:-0x71D6687F38b93CCad569Fa6352c876eea967201b}"
cUSDT="${CHAIN138_D3_CUSDT_ADDRESS:-0x93E66202A11B1772E55407B32B44e5Cd8eda7f22}"
cUSDC="${CHAIN138_D3_CUSDC_ADDRESS:-0xf22258f57794CC8E06237084b353Ab30fFfa640b}"
USDT_USD_FEED="${CHAIN138_D3_USDT_USD_FEED:-0x7c2Cb2667f0f97f4004aae04B67d94A085E6f0f1}"
USDC_USD_FEED="${CHAIN138_D3_USDC_USD_FEED:-0xf072Ac13D45e6c83296ca18F3E04185B747DD6aa}"
cUSDT_USD_FEED="${CHAIN138_D3_CUSDT_USD_FEED:-0x7c96E66F4a0713e327F9e73Cf2721f13DB29036C}"
cUSDC_USD_FEED="${CHAIN138_D3_CUSDC_USD_FEED:-0x291694095232CA80077125F64f6f73076e7910C1}"
BOOTSTRAP_USDT_MOCK="${CHAIN138_D3_BOOTSTRAP_USDT_USD_MOCK:-0x8c5eD794399C68468985238Fa127958E06e6e87F}"
BOOTSTRAP_USDC_MOCK="${CHAIN138_D3_BOOTSTRAP_USDC_USD_MOCK:-0x102BAe3eBf3C8B93445F4B30c90728fd933CeC84}"
BOOTSTRAP_cUSDT_MOCK="${CHAIN138_D3_BOOTSTRAP_CUSDT_USD_MOCK:-0xd493Bb05e9ddf954E8285c6358ED03a2672A006d}"
BOOTSTRAP_cUSDC_MOCK="${CHAIN138_D3_BOOTSTRAP_CUSDC_USD_MOCK:-0xcA85aFc0E24A8ebd69E138928567DcD363758E7A}"
ALLOW_BOOTSTRAP_STABLE_MOCKS="${CHAIN138_D3_ALLOW_BOOTSTRAP_STABLE_MOCKS:-0}"
PROBE_AMOUNT="${CHAIN138_D3_PROBE_SELL_AMOUNT:-100000000000000000}"
MIN_QUOTE_OUT="${CHAIN138_D3_MIN_QUOTE_OUT:-100000000}"
BLOCKSCOUT_INTERNAL_API_BASE="${CHAIN138_BLOCKSCOUT_INTERNAL_API_BASE:-http://${IP_BLOCKSCOUT:-192.168.11.140}:4000/api/v2}"
BLOCKSCOUT_PUBLIC_API_BASE="${CHAIN138_BLOCKSCOUT_PUBLIC_API_BASE:-https://explorer.d-bis.org/api/v2}"
log() {
printf '%s\n' "$*"
}
ok() {
printf '[ok] %s\n' "$*"
}
warn() {
printf '[warn] %s\n' "$*" >&2
}
fail() {
printf '[fail] %s\n' "$*" >&2
exit 1
}
format_units() {
local value="$1"
local decimals="$2"
if [[ "${value}" =~ ^- ]]; then
local positive="${value#-}"
printf -- "-%s" "$(format_units "${positive}" "${decimals}")"
return 0
fi
if [[ "${decimals}" -eq 0 ]]; then
printf '%s' "${value}"
return 0
fi
local len="${#value}"
if (( len <= decimals )); then
local pad_count=$((decimals - len))
local zeros=""
if (( pad_count > 0 )); then
zeros=$(printf '%0*d' "${pad_count}" 0)
fi
local fraction="${zeros}${value}"
fraction="${fraction%"${fraction##*[!0]}"}"
if [[ -z "${fraction}" ]]; then
printf '0'
else
printf '0.%s' "${fraction}"
fi
return 0
fi
local whole="${value:0:len-decimals}"
local fraction="${value:len-decimals}"
fraction="${fraction%"${fraction##*[!0]}"}"
if [[ -z "${fraction}" ]]; then
printf '%s' "${whole}"
else
printf '%s.%s' "${whole}" "${fraction}"
fi
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1"
}
require_command cast
if command -v curl >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
HAS_BLOCKSCOUT_TOOLS=1
else
HAS_BLOCKSCOUT_TOOLS=0
fi
check_stable_feed() {
local label="$1"
local token_address="$2"
local expected_feed="$3"
local bootstrap_feed="$4"
local expected_desc="$5"
mapfile -t source < <(
cast call --rpc-url "${RPC_URL}" "${D3_ORACLE}" \
"priceSources(address)(address,bool,uint256,uint8,uint8,uint256)" "${token_address}"
) || fail "Failed to read ${label} price source from D3Oracle."
(( ${#source[@]} >= 6 )) || fail "Unexpected ${label} priceSources response length: ${#source[@]}"
local feed_address="${source[0]}"
local whitelisted="${source[1]}"
[[ "${feed_address}" != "0x0000000000000000000000000000000000000000" ]] || fail "${label} feed address is zero."
[[ "${whitelisted}" == "true" ]] || fail "${label} feed is not whitelisted."
if [[ "${feed_address,,}" == "${bootstrap_feed,,}" ]]; then
if [[ "${ALLOW_BOOTSTRAP_STABLE_MOCKS}" == "1" ]]; then
warn "${label} still points at bootstrap mock feed ${bootstrap_feed}."
else
fail "${label} still points at bootstrap mock feed ${bootstrap_feed}."
fi
fi
[[ "${feed_address,,}" == "${expected_feed,,}" ]] || fail "${label} expected managed feed ${expected_feed}, got ${feed_address}."
local description
description="$(cast call --rpc-url "${RPC_URL}" "${feed_address}" "description()(string)")" || fail "Failed to read ${label} feed description."
[[ "${description}" == "\"${expected_desc}\"" ]] || fail "${label} feed description mismatch: ${description}"
mapfile -t round_data < <(
cast call --rpc-url "${RPC_URL}" "${feed_address}" \
"latestRoundData()(uint80,int256,uint256,uint256,uint80)"
) || fail "Failed to read ${label} latestRoundData."
(( ${#round_data[@]} >= 5 )) || fail "Unexpected ${label} latestRoundData length: ${#round_data[@]}"
local answer="${round_data[1]%% *}"
[[ "${answer}" == "100000000" ]] || fail "${label} expected 1.00000000 USD answer, got ${answer}."
ok "${label} uses managed feed ${feed_address} (${expected_desc}) with answer ${answer}."
}
check_blockscout_verified() {
local label="$1"
local addr="$2"
local expected_name="$3"
local json name compiler base
json=""
for base in "${BLOCKSCOUT_INTERNAL_API_BASE}" "${BLOCKSCOUT_PUBLIC_API_BASE}"; do
for _ in 1 2 3; do
json="$(curl --max-time 15 -fsS "${base}/smart-contracts/${addr}" 2>/dev/null || true)"
if [[ -n "${json}" ]] && jq -e 'type == "object"' >/dev/null 2>&1 <<<"${json}"; then
break 2
fi
json=""
sleep 2
done
done
[[ -n "${json}" ]] || fail "Failed to read Blockscout metadata for ${label} (${addr})."
name="$(jq -r '.name // empty' <<<"${json}")"
compiler="$(jq -r '.compiler_version // empty' <<<"${json}")"
[[ -n "${name}" ]] || fail "${label} is not source-verified on Blockscout."
[[ -n "${compiler}" ]] || fail "${label} is missing compiler metadata on Blockscout."
[[ "${name}" == "${expected_name}" ]] || fail "${label} expected Blockscout name ${expected_name}, got ${name}."
ok "${label} is Blockscout-verified as ${name} (${compiler})."
}
check_blockscout_verified_or_warn_bytecode_only() {
local label="$1"
local addr="$2"
local expected_name="$3"
local json name compiler base
json=""
for base in "${BLOCKSCOUT_INTERNAL_API_BASE}" "${BLOCKSCOUT_PUBLIC_API_BASE}"; do
for _ in 1 2 3; do
json="$(curl --max-time 15 -fsS "${base}/smart-contracts/${addr}" 2>/dev/null || true)"
if [[ -n "${json}" ]] && jq -e 'type == "object"' >/dev/null 2>&1 <<<"${json}"; then
break 2
fi
json=""
sleep 2
done
done
[[ -n "${json}" ]] || fail "Failed to read Blockscout metadata for ${label} (${addr})."
name="$(jq -r '.name // empty' <<<"${json}")"
compiler="$(jq -r '.compiler_version // empty' <<<"${json}")"
if [[ -n "${name}" && -n "${compiler}" && "${name}" == "${expected_name}" ]]; then
ok "${label} is Blockscout-verified as ${name} (${compiler})."
return 0
fi
if jq -e '.creation_bytecode and .deployed_bytecode' >/dev/null 2>&1 <<<"${json}"; then
warn "${label} currently exposes only bytecode metadata on Blockscout; source-verification submission exists but explorer metadata has not fully materialized."
return 0
fi
fail "${label} is not source-verified on Blockscout."
}
log "Chain 138 DODO v3 verification"
log "RPC: ${RPC_URL}"
log "D3Oracle: ${D3_ORACLE}"
log "D3Vault: ${D3_VAULT}"
log "D3MMFactory: ${D3_FACTORY}"
log "D3Proxy: ${D3_PROXY}"
log "Canonical D3MM: ${D3MM}"
log
version="$(cast call --rpc-url "${RPC_URL}" "${D3MM}" "version()(string)")" || fail "Failed to read D3MM version."
[[ "${version}" == "\"D3MM 1.0.0\"" ]] || fail "Unexpected D3MM version: ${version}"
ok "Canonical pool reports version ${version}."
vault_recognizes_pool="$(cast call --rpc-url "${RPC_URL}" "${D3_VAULT}" "allPoolAddrMap(address)(bool)" "${D3MM}")" || fail "Failed to read vault pool map."
[[ "${vault_recognizes_pool}" == "true" ]] || fail "D3Vault does not recognize canonical D3MM ${D3MM}."
ok "D3Vault recognizes the canonical D3MM."
mapfile -t oracle_source < <(
cast call --rpc-url "${RPC_URL}" "${D3_ORACLE}" \
"priceSources(address)(address,bool,uint256,uint8,uint8,uint256)" "${WETH10}"
) || fail "Failed to read WETH10 price source from D3Oracle."
(( ${#oracle_source[@]} >= 6 )) || fail "Unexpected D3Oracle priceSources response length: ${#oracle_source[@]}"
oracle_address="${oracle_source[0]}"
oracle_whitelisted="${oracle_source[1]}"
price_tolerance="${oracle_source[2]%% *}"
price_decimals="${oracle_source[3]%% *}"
token_decimals="${oracle_source[4]%% *}"
heartbeat="${oracle_source[5]%% *}"
[[ "${oracle_address}" != "0x0000000000000000000000000000000000000000" ]] || fail "WETH10 oracle address is zero."
[[ "${oracle_whitelisted}" == "true" ]] || fail "WETH10 oracle is not whitelisted."
[[ "${oracle_address,,}" != "${BOOTSTRAP_WETH_MOCK,,}" ]] || fail "WETH10 still points at bootstrap mock feed ${BOOTSTRAP_WETH_MOCK}."
[[ "${oracle_address,,}" == "${WETH_USD_FEED,,}" ]] || fail "WETH10 expected oracle feed ${WETH_USD_FEED}, got ${oracle_address}."
(( heartbeat >= 100000 )) || fail "WETH10 heartbeat ${heartbeat} is unexpectedly low."
weth_desc="$(cast call --rpc-url "${RPC_URL}" "${oracle_address}" "description()(string)")" || fail "Failed to read WETH10 feed description."
[[ "${weth_desc}" == "\"ETH/USD Price Feed\"" ]] || fail "Unexpected WETH10 feed description: ${weth_desc}"
ok "WETH10 oracle is configured at ${oracle_address} (${weth_desc}, tolerance=${price_tolerance}, priceDecimals=${price_decimals}, tokenDecimals=${token_decimals}, heartbeat=${heartbeat})."
mapfile -t quote_probe < <(
cast call --rpc-url "${RPC_URL}" "${D3MM}" \
"querySellTokens(address,address,uint256)(uint256,uint256,uint256,uint256,uint256)" \
"${WETH10}" "${USDT}" "${PROBE_AMOUNT}"
) || fail "Failed to query WETH10 -> USDT on canonical D3MM."
(( ${#quote_probe[@]} >= 5 )) || fail "Unexpected D3MM quote response length: ${#quote_probe[@]}"
pay_from="${quote_probe[0]%% *}"
receive_to="${quote_probe[1]%% *}"
vusd_amount="${quote_probe[2]%% *}"
swap_fee="${quote_probe[3]%% *}"
mt_fee="${quote_probe[4]%% *}"
[[ "${pay_from}" == "${PROBE_AMOUNT}" ]] || fail "D3MM returned unexpected payFromAmount ${pay_from} for probe amount ${PROBE_AMOUNT}."
[[ "${receive_to}" != "0" ]] || fail "Canonical D3MM returned zero receive amount for WETH10 -> USDT."
if [[ "${receive_to}" =~ ^[0-9]+$ ]] && [[ "${MIN_QUOTE_OUT}" =~ ^[0-9]+$ ]]; then
if (( receive_to < MIN_QUOTE_OUT )); then
fail "Canonical D3MM quote ${receive_to} is below expected floor ${MIN_QUOTE_OUT}."
fi
else
warn "Skipping numeric threshold comparison because quote or floor was not numeric."
fi
ok "Quote probe WETH10 -> USDT for $(format_units "${PROBE_AMOUNT}" 18) WETH10 returned $(format_units "${receive_to}" 6) USDT (vusd=${vusd_amount}, swapFee=$(format_units "${swap_fee}" 6), mtFee=$(format_units "${mt_fee}" 6))."
check_stable_feed "USDT" "${USDT}" "${USDT_USD_FEED}" "${BOOTSTRAP_USDT_MOCK}" "USDT / USD"
check_stable_feed "USDC" "${USDC}" "${USDC_USD_FEED}" "${BOOTSTRAP_USDC_MOCK}" "USDC / USD"
check_stable_feed "cUSDT" "${cUSDT}" "${cUSDT_USD_FEED}" "${BOOTSTRAP_cUSDT_MOCK}" "cUSDT / USD"
check_stable_feed "cUSDC" "${cUSDC}" "${cUSDC_USD_FEED}" "${BOOTSTRAP_cUSDC_MOCK}" "cUSDC / USD"
if [[ "${HAS_BLOCKSCOUT_TOOLS}" == "1" ]]; then
check_blockscout_verified "D3Oracle" "${D3_ORACLE}" "D3Oracle"
check_blockscout_verified "D3Vault" "${D3_VAULT}" "D3Vault"
check_blockscout_verified_or_warn_bytecode_only "D3MMFactory" "${D3_FACTORY}" "D3MMFactory"
check_blockscout_verified_or_warn_bytecode_only "D3Proxy" "${D3_PROXY}" "D3Proxy"
check_blockscout_verified "DODOApprove" "${DODO_APPROVE}" "DODOApprove"
check_blockscout_verified "DODOApproveProxy" "${DODO_APPROVE_PROXY}" "DODOApproveProxy"
else
warn "Skipping Blockscout source-verification checks because curl or jq is missing."
fi
log
log "Result: Chain 138 DODO v3 canonical WETH10 pool and managed stable feeds look healthy."