- Resolve stash: merge load_deployment_env path with secure-secrets and CR/LF RPC strip - create-pmm-full-mesh-chain138.sh delegates to sync-chain138-pmm-pools-from-json.sh - env.additions.example: canonical PMM pool defaults (cUSDT/USDT per crosscheck) - Include Chain138 scripts, official mirror deploy scaffolding, and prior staged changes Made-with: Cursor
318 lines
12 KiB
Bash
Executable File
318 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Update Oracle Contract with Current ETH/USD Price
|
|
# This script fetches the current ETH price from CoinGecko and updates the oracle contract
|
|
# Usage: ./scripts/update-oracle-price.sh [rpc-url] [oracle-address] [private-key]
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
# Load .env if available
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
cd "$SCRIPT_DIR/.." || exit 1
|
|
if [ -f .env ]; then
|
|
set -a
|
|
source .env
|
|
set +a
|
|
fi
|
|
|
|
# RPC URL priority: command arg > RPC_URL_138 > RPC_URL > working defaults
|
|
DEFAULT_RPC="http://192.168.11.211:8545"
|
|
RPC_URL="${1:-${RPC_URL_138:-${RPC_URL:-$DEFAULT_RPC}}}"
|
|
ORACLE_ADDRESS="${2:-0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6}"
|
|
AGGREGATOR_ADDRESS="${AGGREGATOR_ADDRESS:-0x99b3511a2d315a497c8112c1fdd8d508d4b1e506}"
|
|
PRIVATE_KEY="${3:-${PRIVATE_KEY:-${DEPLOYER_PRIVATE_KEY:-}}}"
|
|
|
|
# Test RPC and fallback if needed
|
|
if ! curl -s -X POST -H "Content-Type: application/json" \
|
|
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
|
|
--max-time 3 "$RPC_URL" > /dev/null 2>&1; then
|
|
log_warn "Primary RPC not accessible, trying alternatives..."
|
|
# Try alternative RPCs
|
|
for ALT_RPC in "http://192.168.11.211:8545" "https://rpc-http-pub.d-bis.org"; do
|
|
if curl -s -X POST -H "Content-Type: application/json" \
|
|
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
|
|
--max-time 3 "$ALT_RPC" > /dev/null 2>&1; then
|
|
log_info "Using alternative RPC: $ALT_RPC"
|
|
RPC_URL="$ALT_RPC"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ -z "$PRIVATE_KEY" ]; then
|
|
log_error "Private key required"
|
|
log_info "Usage: $0 [rpc-url] [oracle-address] [private-key]"
|
|
log_info " OR: Set DEPLOYER_PRIVATE_KEY environment variable"
|
|
exit 1
|
|
fi
|
|
|
|
echo "========================================="
|
|
echo "Update Oracle Price Feed"
|
|
echo "========================================="
|
|
echo ""
|
|
|
|
log_info "RPC URL: $RPC_URL"
|
|
log_info "Oracle Address: $ORACLE_ADDRESS"
|
|
echo ""
|
|
|
|
# Check RPC connectivity
|
|
log_info "Checking RPC connectivity..."
|
|
if ! curl -s -X POST -H "Content-Type: application/json" \
|
|
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
|
|
"$RPC_URL" >/dev/null 2>&1; then
|
|
log_error "RPC is not accessible at $RPC_URL"
|
|
exit 1
|
|
fi
|
|
log_success "RPC is accessible"
|
|
|
|
# Besu / some nodes reject typed `cast call`; use eth_call + calldata.
|
|
eth_call_raw() {
|
|
local to="$1"
|
|
local sig="$2"
|
|
shift 2
|
|
local data
|
|
data=$(cast calldata "$sig" "$@") || return 1
|
|
cast rpc eth_call "{\"to\":\"$to\",\"data\":\"$data\"}" latest --rpc-url "$RPC_URL" 2>/dev/null | tr -d '\n\"' || true
|
|
}
|
|
|
|
abi_decode_bool() {
|
|
local h="${1:-0x0}"
|
|
python3 -c "x=int('${h}',16); print('true' if x else 'false')"
|
|
}
|
|
|
|
# Fetch ETH/USD: try Pro API when key is set; fall back to public CoinGecko if Pro errors or rate-limits.
|
|
# CoinGecko may 429 without a descriptive User-Agent when many requests share an IP.
|
|
CG_UA="${COINGECKO_USER_AGENT:-proxmox-chain138-oracle/1.0 (dbis-138)}"
|
|
log_info "Fetching ETH/USD price from CoinGecko..."
|
|
ETH_PRICE="0"
|
|
# Demo keys: api.coingecko.com + x-cg-demo-api-key. Paid Pro: pro-api.coingecko.com + x-cg-pro-api-key.
|
|
if [ -n "${COINGECKO_API_KEY:-}" ]; then
|
|
ETH_PRICE=$(curl -s --max-time 20 -A "$CG_UA" -H "x-cg-demo-api-key: $COINGECKO_API_KEY" \
|
|
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd' | \
|
|
python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get('ethereum', {}).get('usd', '0'))" 2>/dev/null || echo "0")
|
|
fi
|
|
if [ "$ETH_PRICE" = "0" ] || [ -z "$ETH_PRICE" ]; then
|
|
if [ -n "${COINGECKO_API_KEY:-}" ]; then
|
|
ETH_PRICE=$(curl -s --max-time 20 -A "$CG_UA" -H "x-cg-pro-api-key: $COINGECKO_API_KEY" \
|
|
'https://pro-api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd' | \
|
|
python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get('ethereum', {}).get('usd', '0'))" 2>/dev/null || echo "0")
|
|
fi
|
|
fi
|
|
if [ "$ETH_PRICE" = "0" ] || [ -z "$ETH_PRICE" ]; then
|
|
log_info "Authenticated CoinGecko unset or returned no price; trying public CoinGecko..."
|
|
ETH_PRICE=$(curl -s --max-time 20 -A "$CG_UA" 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd' | \
|
|
python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get('ethereum', {}).get('usd', '0'))" 2>/dev/null || echo "0")
|
|
fi
|
|
|
|
if [ "$ETH_PRICE" = "0" ] || [ -z "$ETH_PRICE" ]; then
|
|
log_error "Failed to fetch ETH price from CoinGecko"
|
|
log_info "Trying Binance API as fallback..."
|
|
ETH_PRICE=$(curl -s --max-time 20 'https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT' | \
|
|
python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get('price', '0'))" 2>/dev/null || echo "0")
|
|
fi
|
|
|
|
if [ "$ETH_PRICE" = "0" ] || [ -z "$ETH_PRICE" ]; then
|
|
log_error "Failed to fetch ETH price from all sources"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "ETH/USD Price: \$$ETH_PRICE"
|
|
|
|
# Convert to 8 decimals (oracle format)
|
|
PRICE_DECIMALS=$(python3 << PYEOF
|
|
price = float("$ETH_PRICE")
|
|
decimals = int(price * 100000000)
|
|
print(decimals)
|
|
PYEOF
|
|
)
|
|
|
|
log_info "Price in 8 decimals: $PRICE_DECIMALS"
|
|
echo ""
|
|
|
|
# Latest round: prefer aggregator (proxy at ORACLE_ADDRESS often returns empty on Besu)
|
|
LR_READ_CONTRACT="${ORACLE_LATEST_ROUND_CONTRACT:-$AGGREGATOR_ADDRESS}"
|
|
|
|
# Check current oracle price (eth_call — Besu-safe)
|
|
log_info "Checking current oracle price (source $LR_READ_CONTRACT)..."
|
|
LR_RAW="$(eth_call_raw "$LR_READ_CONTRACT" "latestRoundData()")"
|
|
CURRENT_ORACLE_DATA=""
|
|
if [ -n "$LR_RAW" ] && [ "$LR_RAW" != "0x" ]; then
|
|
read -r LR_ROUND LR_ANS_INT <<< "$(python3 -c "
|
|
raw = '$LR_RAW'.lower().replace('0x', '')
|
|
if len(raw) < 128:
|
|
print('0 0')
|
|
raise SystemExit(0)
|
|
rid = int(raw[0:64], 16)
|
|
ans = int(raw[64:128], 16)
|
|
if ans >= 2**255:
|
|
ans -= 2**256
|
|
print(rid, ans)
|
|
")"
|
|
if [ "${LR_ROUND:-0}" != "0" ] || [ "${LR_ANS_INT:-0}" != "0" ]; then
|
|
CURRENT_ORACLE_DATA="$LR_RAW"
|
|
CURRENT_PRICE=$(python3 -c "print(${LR_ANS_INT:-0} / 100000000.0)")
|
|
log_info "Current oracle price: \$$(printf '%.2f' "$CURRENT_PRICE")"
|
|
PRICE_DIFF=$(python3 -c "c=float('$CURRENT_PRICE'); n=float('$ETH_PRICE'); print(abs(n-c)/c*100 if c else 999)")
|
|
if python3 -c "import sys; sys.exit(0 if float('$PRICE_DIFF') < 1 else 1)"; then
|
|
log_warn "Price difference is less than 1% (${PRICE_DIFF}%)"
|
|
log_info "Skipping update to save gas"
|
|
exit 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Update oracle contract
|
|
log_info "Updating oracle contract..."
|
|
log_info "This will send a transaction to update the price feed..."
|
|
|
|
# Check if updateAnswer function exists, if not try transmit
|
|
if cast sig "updateAnswer(int256)" >/dev/null 2>&1; then
|
|
UPDATE_METHOD="updateAnswer"
|
|
elif cast sig "transmit(int256)" >/dev/null 2>&1; then
|
|
UPDATE_METHOD="transmit"
|
|
else
|
|
log_error "Could not determine oracle update method"
|
|
log_info "Please check the oracle contract ABI"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Using method: $UPDATE_METHOD"
|
|
|
|
# Check if account is authorized transmitter
|
|
if [ -n "$PRIVATE_KEY" ]; then
|
|
DEPLOYER_ADDR=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || echo "")
|
|
if [ -n "$DEPLOYER_ADDR" ]; then
|
|
log_info "Checking transmitter authorization..."
|
|
TXRAW="$(eth_call_raw "$AGGREGATOR_ADDRESS" "isTransmitter(address)" "$DEPLOYER_ADDR")"
|
|
IS_TRANSMITTER="$(abi_decode_bool "${TXRAW:-0x0}")"
|
|
if [ "$IS_TRANSMITTER" != "true" ]; then
|
|
log_warn "Account $DEPLOYER_ADDR is NOT an authorized transmitter"
|
|
log_info "Oracle requires transmitter role to update prices"
|
|
log_info "Options:"
|
|
log_info " 1. Use Oracle Publisher service (VMID 3500) - recommended"
|
|
log_info " 2. Use an authorized transmitter account private key"
|
|
log_info " 3. Add this account as transmitter (requires admin)"
|
|
log_info ""
|
|
log_info "Checking for transmitter addresses..."
|
|
TX_COUNT=0
|
|
for i in 0 1 2 3 4 5; do
|
|
TRAW="$(eth_call_raw "$AGGREGATOR_ADDRESS" "transmitters(uint256)" "$i")"
|
|
TX_ADDR=""
|
|
if [ -n "$TRAW" ] && [ "$TRAW" != "0x" ]; then
|
|
TX_ADDR="$(TRAW="$TRAW" python3 -c "import os; h=os.environ['TRAW'].lower().replace('0x',''); print(('0x'+h[-40:]) if len(h)>=40 else '')")"
|
|
fi
|
|
if [ -n "$TX_ADDR" ] && [ "$TX_ADDR" != "0x0000000000000000000000000000000000000000" ]; then
|
|
log_info " Transmitter $TX_COUNT: $TX_ADDR"
|
|
TX_COUNT=$((TX_COUNT + 1))
|
|
fi
|
|
done
|
|
if [ $TX_COUNT -eq 0 ]; then
|
|
log_error "No transmitters found - oracle may not be configured"
|
|
fi
|
|
exit 1
|
|
else
|
|
log_success "Account is authorized transmitter"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Use aggregator address for updates (proxy is read-only)
|
|
UPDATE_CONTRACT="$AGGREGATOR_ADDRESS"
|
|
log_info "Updating aggregator contract: $UPDATE_CONTRACT"
|
|
|
|
# Send transaction (with timeout). Do not use `set -e` inside $() — capture status explicitly.
|
|
log_info "Sending transaction to aggregator (this may take 30-60 seconds)..."
|
|
GAS_PRICE_WEI="${ORACLE_UPDATE_GAS_PRICE_WEI:-1000000000}"
|
|
GAS_LIMIT_ORACLE="${ORACLE_UPDATE_GAS_LIMIT:-400000}"
|
|
set +e
|
|
TX_OUTPUT=$(timeout 90 cast send "$UPDATE_CONTRACT" \
|
|
"$UPDATE_METHOD(uint256)" \
|
|
"$PRICE_DECIMALS" \
|
|
--rpc-url "$RPC_URL" \
|
|
--private-key "$PRIVATE_KEY" \
|
|
--legacy \
|
|
--gas-price "$GAS_PRICE_WEI" \
|
|
--gas-limit "$GAS_LIMIT_ORACLE" \
|
|
2>&1)
|
|
TX_STAT=$?
|
|
set -e
|
|
if [ "$TX_STAT" -ne 0 ]; then
|
|
log_error "Transaction failed or timed out (exit $TX_STAT)"
|
|
echo "$TX_OUTPUT"
|
|
exit 1
|
|
fi
|
|
|
|
# Extract transaction hash from output
|
|
TX_HASH=$(echo "$TX_OUTPUT" | grep -oE "transactionHash[[:space:]]+0x[0-9a-fA-F]{64}" | awk '{print $2}' || \
|
|
echo "$TX_OUTPUT" | grep -oE "0x[0-9a-fA-F]{64}" | head -1 || echo "")
|
|
|
|
# If still no hash, check for error messages
|
|
if [ -z "$TX_HASH" ]; then
|
|
if echo "$TX_OUTPUT" | grep -qi "error\|failed\|revert"; then
|
|
log_error "Transaction failed:"
|
|
echo "$TX_OUTPUT"
|
|
exit 1
|
|
elif echo "$TX_OUTPUT" | grep -qi "insufficient funds\|gas"; then
|
|
log_error "Transaction failed (gas/funds issue):"
|
|
echo "$TX_OUTPUT"
|
|
exit 1
|
|
else
|
|
log_warn "Could not extract transaction hash from output:"
|
|
echo "$TX_OUTPUT" | head -20
|
|
log_info "Transaction may have been sent - check explorer or try again"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$TX_HASH" ]; then
|
|
log_error "Failed to send transaction"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Transaction sent: $TX_HASH"
|
|
|
|
# Wait for confirmation
|
|
log_info "Waiting for transaction confirmation..."
|
|
sleep 5
|
|
|
|
# Verify update
|
|
log_info "Verifying oracle update..."
|
|
NEW_LR="$(eth_call_raw "$LR_READ_CONTRACT" "latestRoundData()")"
|
|
NEW_ORACLE_DATA="$NEW_LR"
|
|
|
|
if [ -n "$NEW_ORACLE_DATA" ] && [ "$NEW_ORACLE_DATA" != "0x" ]; then
|
|
read -r _ NEW_ANS_INT <<< "$(python3 -c "
|
|
raw = '$NEW_LR'.lower().replace('0x', '')
|
|
if len(raw) < 128:
|
|
print('0 0')
|
|
raise SystemExit(0)
|
|
ans = int(raw[64:128], 16)
|
|
if ans >= 2**255:
|
|
ans -= 2**256
|
|
print(0, ans)
|
|
")"
|
|
NEW_PRICE=$(python3 -c "print(${NEW_ANS_INT:-0} / 100000000.0)")
|
|
log_success "Oracle updated successfully!"
|
|
log_info "New oracle price: \$$(printf '%.2f' $NEW_PRICE)"
|
|
else
|
|
log_warn "Could not verify oracle update (may need more time for confirmation)"
|
|
fi
|
|
|
|
echo ""
|
|
log_success "========================================="
|
|
log_success "Oracle Price Update Complete!"
|
|
log_success "========================================="
|
|
echo ""
|
|
|