#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" WITH_CUTOVER=false DRY_RUN=false for arg in "$@"; do case "$arg" in --with-cutover) WITH_CUTOVER=true ;; --dry-run) DRY_RUN=true ;; *) echo "Unknown option: $arg" >&2 echo "Usage: $0 [--dry-run] [--with-cutover]" >&2 exit 1 ;; esac done source "$SMOM_ROOT/scripts/load-env.sh" >/dev/null 2>&1 command -v cast >/dev/null 2>&1 || { echo "cast is required" >&2; exit 1; } command -v forge >/dev/null 2>&1 || { echo "forge is required" >&2; exit 1; } command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2; exit 1; } ENV_FILE="$SMOM_ROOT/.env" DEPLOYER_ADDRESS="$(cast wallet address "$PRIVATE_KEY")" RPC="$RPC_URL_138" CW_VAULT="${CW_STABLECOIN_RESERVE_VAULT:-}" TARGET_USDT_BACKING="${CW_VAULT_SEED_USDT_AMOUNT:-}" TARGET_USDC_BACKING="${CW_VAULT_SEED_USDC_AMOUNT:-}" log() { printf '[cw-vault] %s\n' "$*" } redact_secrets() { local text="$1" if [[ -n "${PRIVATE_KEY:-}" ]]; then text="${text//${PRIVATE_KEY}/\$PRIVATE_KEY}" fi printf '%s\n' "$text" } format_command() { printf '%q ' "$@" printf '\n' } run_command() { if $DRY_RUN; then redact_secrets "$(format_command "$@")" else "$@" fi } has_code() { local address="$1" [[ -n "$address" ]] || return 1 local code code="$(cast code "$address" --rpc-url "$RPC" 2>/dev/null || true)" [[ -n "$code" && "$code" != "0x" ]] } send_cast() { local to="$1" local signature="$2" shift 2 local cmd=(cast send) local gas_price gas_price="$(cast gas-price --rpc-url "$RPC" 2>/dev/null || true)" if [[ "$gas_price" =~ ^[0-9]+$ && "$gas_price" -gt 0 ]]; then gas_price=$((gas_price + gas_price / 5 + 1000000)) cmd+=(--gas-price "$gas_price") fi cmd+=(--rpc-url "$RPC" --private-key "$PRIVATE_KEY" --legacy "$to" "$signature") if [[ "$#" -gt 0 ]]; then cmd+=("$@") fi run_command "${cmd[@]}" } set_env_value() { local file="$1" local key="$2" local value="$3" local escaped_value escaped_value="$(printf '%s' "$value" | sed -e 's/[\\/&]/\\&/g')" if grep -q "^${key}=" "$file"; then sed -i "s/^${key}=.*/${key}=${escaped_value}/" "$file" else printf '%s=%s\n' "$key" "$value" >>"$file" fi } deploy_from_broadcast() { local script_file="$1" local script_contract="$2" local extra_env="$3" local broadcast_path="$SMOM_ROOT/broadcast/${script_file}/138/run-latest.json" if $DRY_RUN; then redact_secrets "$extra_env forge script script/reserve/${script_file}:${script_contract} --rpc-url \"$RPC\" --broadcast --private-key \"$PRIVATE_KEY\" --legacy -vvv" return 0 fi ( cd "$SMOM_ROOT" eval "$extra_env forge script script/reserve/${script_file}:${script_contract} --rpc-url \"$RPC\" --broadcast --private-key \"\$PRIVATE_KEY\" --legacy -vvv >/tmp/${script_contract}.log" ) jq -r '.transactions[] | select(.transactionType == "CREATE") | .contractAddress' "$broadcast_path" | tail -1 } call_uint() { cast call "$1" "$2" "${@:3}" --rpc-url "$RPC" } call_addr() { cast call "$1" "$2" --rpc-url "$RPC" } normalize_uint() { printf '%s\n' "$1" | awk '{print $1}' } ensure_vault() { if has_code "$CW_VAULT"; then log "Using existing stablecoin reserve vault $CW_VAULT" return fi log "Deploying stablecoin reserve vault" if $DRY_RUN; then deploy_from_broadcast "DeployStablecoinReserveVault.s.sol" "DeployStablecoinReserveVault" "" CW_VAULT="0x0000000000000000000000000000000000000140" return fi CW_VAULT="$( deploy_from_broadcast "DeployStablecoinReserveVault.s.sol" "DeployStablecoinReserveVault" "" )" set_env_value "$ENV_FILE" "CW_STABLECOIN_RESERVE_VAULT" "$CW_VAULT" } ensure_official_balance() { local token="$1" local amount_needed="$2" local label="$3" local owner balance shortfall owner="$(cast call "$token" "owner()(address)" --rpc-url "$RPC")" balance="$(normalize_uint "$(cast call "$token" "balanceOf(address)(uint256)" "$DEPLOYER_ADDRESS" --rpc-url "$RPC")")" if [[ "$balance" =~ ^[0-9]+$ ]] && (( balance >= amount_needed )); then log "$label deployer balance already sufficient: $balance" return fi shortfall=$((amount_needed - balance)) if [[ "${owner,,}" != "${DEPLOYER_ADDRESS,,}" ]]; then echo "Deployer is not owner of $label token $token; cannot mint shortfall $shortfall" >&2 exit 1 fi if (( shortfall > 0 )); then send_cast "$token" "mint(address,uint256)" "$DEPLOYER_ADDRESS" "$shortfall" fi } ensure_seeded_backing() { local official_token="$1" local reserve_getter="$2" local seed_signature="$3" local target_amount="$4" local label="$5" local current_reserve shortfall if $DRY_RUN; then current_reserve=0 else current_reserve="$(normalize_uint "$(cast call "$CW_VAULT" "$reserve_getter" --rpc-url "$RPC")")" fi if [[ -z "$target_amount" ]]; then target_amount="$(normalize_uint "$(cast call "$official_token" "totalSupply()(uint256)" --rpc-url "$RPC")")" else target_amount="$(normalize_uint "$target_amount")" fi if (( current_reserve >= target_amount )); then log "$label reserve already seeded: $current_reserve" return fi shortfall=$((target_amount - current_reserve)) ensure_official_balance "$official_token" "$shortfall" "$label" send_cast "$official_token" "approve(address,uint256)" "$CW_VAULT" "$shortfall" send_cast "$CW_VAULT" "$seed_signature" "$shortfall" } ensure_compliant_owner() { local token="$1" local label="$2" local owner if $DRY_RUN; then owner="$DEPLOYER_ADDRESS" else owner="$(cast call "$token" "owner()(address)" --rpc-url "$RPC")" fi if [[ "${owner,,}" == "${CW_VAULT,,}" ]]; then log "$label ownership already transferred to vault" return fi if [[ "${owner,,}" != "${DEPLOYER_ADDRESS,,}" ]]; then echo "$label owner is $owner, expected deployer or vault" >&2 exit 1 fi send_cast "$token" "transferOwnership(address)" "$CW_VAULT" } verify_vault_state() { local usdt_owner usdc_owner usdt_ratio usdc_ratio usdt_owner="$(cast call "$COMPLIANT_USDT_ADDRESS" "owner()(address)" --rpc-url "$RPC")" usdc_owner="$(cast call "$COMPLIANT_USDC_ADDRESS" "owner()(address)" --rpc-url "$RPC")" usdt_ratio="$(cast call "$CW_VAULT" "getBackingRatio(address)(uint256,uint256,uint256)" "$COMPLIANT_USDT_ADDRESS" --rpc-url "$RPC" | tr '\n' ' ' | xargs)" usdc_ratio="$(cast call "$CW_VAULT" "getBackingRatio(address)(uint256,uint256,uint256)" "$COMPLIANT_USDC_ADDRESS" --rpc-url "$RPC" | tr '\n' ' ' | xargs)" log "cUSDT owner: $usdt_owner" log "cUSDC owner: $usdc_owner" log "cUSDT backing tuple: $usdt_ratio" log "cUSDC backing tuple: $usdc_ratio" } log "Deployer: $DEPLOYER_ADDRESS" log "Dry run: $DRY_RUN" if [[ -z "$TARGET_USDT_BACKING" ]]; then TARGET_USDT_BACKING="$(normalize_uint "$(cast call "$COMPLIANT_USDT_ADDRESS" "totalSupply()(uint256)" --rpc-url "$RPC")")" fi if [[ -z "$TARGET_USDC_BACKING" ]]; then TARGET_USDC_BACKING="$(normalize_uint "$(cast call "$COMPLIANT_USDC_ADDRESS" "totalSupply()(uint256)" --rpc-url "$RPC")")" fi ensure_vault if ! $DRY_RUN; then set_env_value "$ENV_FILE" "CW_STABLECOIN_RESERVE_VAULT" "$CW_VAULT" set_env_value "$ENV_FILE" "CW_REQUIRE_VAULT_BACKING" "1" set_env_value "$ENV_FILE" "CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT" "1" set_env_value "$ENV_FILE" "CW_REQUIRE_RESERVE_SYSTEM_BALANCE" "0" fi ensure_seeded_backing "$OFFICIAL_USDT_ADDRESS" "usdtReserveBalance()(uint256)" "seedUSDTReserve(uint256)" "$TARGET_USDT_BACKING" "USDT" ensure_seeded_backing "$OFFICIAL_USDC_ADDRESS" "usdcReserveBalance()(uint256)" "seedUSDCReserve(uint256)" "$TARGET_USDC_BACKING" "USDC" ensure_compliant_owner "$COMPLIANT_USDT_ADDRESS" "cUSDT" ensure_compliant_owner "$COMPLIANT_USDC_ADDRESS" "cUSDC" if ! $DRY_RUN; then verify_vault_state fi if $WITH_CUTOVER; then if $DRY_RUN; then echo "(cd \"$SMOM_ROOT\" && ./scripts/deployment/complete-nonprefunded-avax-cutover.sh --dry-run)" else (cd "$SMOM_ROOT" && ./scripts/deployment/complete-nonprefunded-avax-cutover.sh) fi fi