- 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
340 lines
14 KiB
Bash
Executable File
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."
|