diff --git a/docs/rollback.md b/docs/rollback.md new file mode 100644 index 0000000..972de05 --- /dev/null +++ b/docs/rollback.md @@ -0,0 +1,103 @@ +# Rollback Procedure + +This document explains how to recover from a failed or bad CosmosVote deployment. + +## Important limitation + +Soroban smart contracts are **immutable once deployed**. A rollback does not remove or modify on-chain contracts. It restores the *contract ID references* in your environment (`.env`) so your application points back to the previously known-good contracts. + +--- + +## How backups work + +Every run of `deploy.sh` or `deploy_mainnet.sh` saves the current contract IDs to a timestamped file before touching anything: + +``` +logs/backups/contracts__.env +``` + +Example: +``` +# CosmosVote contract backup — 2026-06-02T05:00:00Z +# Network: testnet +TOKEN_CONTRACT_ID=CAABC... +GOVERNANCE_CONTRACT_ID=CBXYZ... +``` + +--- + +## Rollback steps + +### 1. List available backups + +```bash +./scripts/rollback.sh +``` + +### 2. Restore the most recent backup + +```bash +./scripts/rollback.sh --latest +``` + +### 3. Restore a specific backup + +```bash +./scripts/rollback.sh logs/backups/contracts_testnet_20260602_050000.env +``` + +The script will: +1. Read `TOKEN_CONTRACT_ID` and `GOVERNANCE_CONTRACT_ID` from the backup. +2. Update `.env` in-place (or print export commands if `.env` is absent). +3. Call `total_supply` and `get_proposal_count` on-chain to confirm the restored contracts respond (requires `STELLAR_SECRET_KEY`). + +--- + +## Post-deployment verification + +`deploy.sh` and `deploy_mainnet.sh` automatically verify both contracts respond before printing the success summary. If verification fails the script exits with an error — check the log file for details. + +To verify manually at any time: + +```bash +# Token contract +stellar contract invoke \ + --id "$TOKEN_CONTRACT_ID" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$STELLAR_RPC_URL" \ + --network-passphrase "" \ + -- total_supply + +# Governance contract +stellar contract invoke \ + --id "$GOVERNANCE_CONTRACT_ID" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$STELLAR_RPC_URL" \ + --network-passphrase "" \ + -- get_proposal_count +``` + +--- + +## Partial deployment recovery + +If deployment failed after the token contract was deployed but before governance was deployed: + +1. Note the `TOKEN_CONTRACT_ID` printed in the logs (`logs/deploy_*.log`). +2. Set `TOKEN_CONTRACT_ID` in `.env` manually. +3. Re-run `deploy.sh` — the script will deploy a fresh governance contract and verify both. + +If the partial deployment left a governance contract initialised against a stale token contract, treat it as dead and redeploy from scratch. Use `rollback.sh --latest` to restore the last known-good pair. + +--- + +## Log files + +All script output is tee'd to timestamped files under `logs/`: + +| File pattern | Script | +|---|---| +| `logs/deploy_*.log` | `deploy.sh` | +| `logs/deploy_mainnet_*.log` | `deploy_mainnet.sh` | +| `logs/rollback_*.log` | `rollback.sh` | +| `logs/backups/contracts_*.env` | Pre-deployment backups | diff --git a/scripts/deploy.sh b/scripts/deploy.sh index a320c85..cf08d1b 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,10 +2,37 @@ # deploy.sh — Deploy CosmosVote contracts to local or testnet set -euo pipefail -# ─── Load environment ──────────────────────────────────────────────────────── +# ─── Logging ───────────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(dirname "$SCRIPT_DIR")" +LOG_DIR="$ROOT_DIR/logs" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/deploy_$(date +%Y%m%d_%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +log() { + local level="$1"; shift + echo "[$(date +%Y-%m-%dT%H:%M:%S)] [$level] $*" +} + +trap 'log ERROR "Script failed at line $LINENO. Check $LOG_FILE for details."' ERR + +# ─── Env validation ────────────────────────────────────────────────────────── +check_required_env() { + local missing=0 + for var in "$@"; do + if [[ -z "${!var:-}" ]]; then + log ERROR "Required environment variable '$var' is unset or empty" + missing=1 + fi + done + [[ $missing -eq 0 ]] || exit 1 +} + +CHECK_ENV_ONLY=false +[[ "${1:-}" == "--check-env" ]] && CHECK_ENV_ONLY=true +# ─── Load environment ──────────────────────────────────────────────────────── if [[ -f "$ROOT_DIR/.env" ]]; then # shellcheck disable=SC1091 source "$ROOT_DIR/.env" @@ -13,7 +40,7 @@ fi NETWORK="${NETWORK:-local}" STELLAR_RPC_URL="${STELLAR_RPC_URL:-http://localhost:8000}" -STELLAR_SECRET_KEY="${STELLAR_SECRET_KEY:?STELLAR_SECRET_KEY must be set}" +STELLAR_SECRET_KEY="${STELLAR_SECRET_KEY:-}" INITIAL_TOKEN_SUPPLY="${INITIAL_TOKEN_SUPPLY:-1000000000}" TOKEN_NAME="${TOKEN_NAME:-CosmosVote}" TOKEN_SYMBOL="${TOKEN_SYMBOL:-VOTE}" @@ -22,6 +49,8 @@ MIN_PROPOSAL_BALANCE="${MIN_PROPOSAL_BALANCE:-0}" PROPOSAL_COOLDOWN="${PROPOSAL_COOLDOWN:-0}" RESTRICT_ADMIN_VOTE="${RESTRICT_ADMIN_VOTE:-false}" +check_required_env STELLAR_SECRET_KEY NETWORK + case "$NETWORK" in local) PASSPHRASE="${STELLAR_NETWORK_PASSPHRASE:-Standalone Network ; February 2021}" @@ -31,42 +60,94 @@ case "$NETWORK" in STELLAR_RPC_URL="https://soroban-testnet.stellar.org" ;; *) - echo "ERROR: Use deploy_mainnet.sh for mainnet deployments." >&2 + log ERROR "Unsupported network '$NETWORK'. Use deploy_mainnet.sh for mainnet deployments." exit 1 ;; esac -echo "=== CosmosVote Deployment ===" -echo "Network : $NETWORK" -echo "RPC URL : $STELLAR_RPC_URL" -echo "" +if $CHECK_ENV_ONLY; then + log INFO "All required environment variables are set." + exit 0 +fi + +# ─── Backup & verification helpers ─────────────────────────────────────────── +BACKUP_DIR="$ROOT_DIR/logs/backups" +mkdir -p "$BACKUP_DIR" + +backup_contracts() { + local stamp; stamp="$(date +%Y%m%d_%H%M%S)" + local backup_file="$BACKUP_DIR/contracts_${NETWORK}_${stamp}.env" + { + echo "# CosmosVote contract backup — $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "# Network: $NETWORK" + echo "TOKEN_CONTRACT_ID=${TOKEN_CONTRACT_ID:-}" + echo "GOVERNANCE_CONTRACT_ID=${GOVERNANCE_CONTRACT_ID:-}" + } > "$backup_file" + log INFO "Pre-deployment backup saved: $backup_file" + echo "$backup_file" +} + +verify_deployment() { + local token_id="$1" gov_id="$2" rpc="$3" passphrase="$4" + log INFO "Verifying token contract ($token_id)..." + stellar contract invoke \ + --id "$token_id" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$rpc" \ + --network-passphrase "$passphrase" \ + -- total_supply >/dev/null \ + || { log ERROR "Token contract verification failed"; return 1; } + + log INFO "Verifying governance contract ($gov_id)..." + stellar contract invoke \ + --id "$gov_id" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$rpc" \ + --network-passphrase "$passphrase" \ + -- get_proposal_count >/dev/null \ + || { log ERROR "Governance contract verification failed"; return 1; } + + log INFO "Post-deployment verification passed." +} + +log INFO "=== CosmosVote Deployment ===" +log INFO "Network : $NETWORK" +log INFO "RPC URL : $STELLAR_RPC_URL" +log INFO "Log file: $LOG_FILE" + +# ─── Pre-deployment backup ──────────────────────────────────────────────────── +backup_contracts # ─── Build ─────────────────────────────────────────────────────────────────── -echo ">>> Building WASM binaries..." +log INFO "Building WASM binaries..." cd "$ROOT_DIR" -cargo build --release --target wasm32-unknown-unknown +cargo build --release --target wasm32-unknown-unknown \ + || { log ERROR "cargo build failed"; exit 1; } TOKEN_WASM="$ROOT_DIR/target/wasm32-unknown-unknown/release/cosmosvote_token.wasm" GOV_WASM="$ROOT_DIR/target/wasm32-unknown-unknown/release/cosmosvote_governance.wasm" +for wasm in "$TOKEN_WASM" "$GOV_WASM"; do + [[ -f "$wasm" ]] || { log ERROR "WASM not found: $wasm"; exit 1; } +done + # ─── Derive admin address ──────────────────────────────────────────────────── ADMIN_ADDRESS=$(stellar keys address --secret-key "$STELLAR_SECRET_KEY" 2>/dev/null || \ - stellar keys address "$STELLAR_SECRET_KEY") - -echo "Admin : $ADMIN_ADDRESS" -echo "" + stellar keys address "$STELLAR_SECRET_KEY") \ + || { log ERROR "Failed to derive admin address from STELLAR_SECRET_KEY"; exit 1; } +log INFO "Admin: $ADMIN_ADDRESS" # ─── Deploy token contract ─────────────────────────────────────────────────── -echo ">>> Deploying token contract..." +log INFO "Deploying token contract..." TOKEN_CONTRACT_ID=$(stellar contract deploy \ --wasm "$TOKEN_WASM" \ --source "$STELLAR_SECRET_KEY" \ --rpc-url "$STELLAR_RPC_URL" \ - --network-passphrase "$PASSPHRASE") + --network-passphrase "$PASSPHRASE") \ + || { log ERROR "Token contract deployment failed"; exit 1; } +log INFO "Token contract ID: $TOKEN_CONTRACT_ID" -echo "Token contract ID: $TOKEN_CONTRACT_ID" - -echo ">>> Initializing token contract..." +log INFO "Initializing token contract..." stellar contract invoke \ --id "$TOKEN_CONTRACT_ID" \ --source "$STELLAR_SECRET_KEY" \ @@ -77,19 +158,20 @@ stellar contract invoke \ --initial_supply "$INITIAL_TOKEN_SUPPLY" \ --name "$TOKEN_NAME" \ --symbol "$TOKEN_SYMBOL" \ - --decimals "$TOKEN_DECIMALS" + --decimals "$TOKEN_DECIMALS" \ + || { log ERROR "Token contract initialization failed"; exit 1; } # ─── Deploy governance contract ────────────────────────────────────────────── -echo ">>> Deploying governance contract..." +log INFO "Deploying governance contract..." GOVERNANCE_CONTRACT_ID=$(stellar contract deploy \ --wasm "$GOV_WASM" \ --source "$STELLAR_SECRET_KEY" \ --rpc-url "$STELLAR_RPC_URL" \ - --network-passphrase "$PASSPHRASE") - -echo "Governance contract ID: $GOVERNANCE_CONTRACT_ID" + --network-passphrase "$PASSPHRASE") \ + || { log ERROR "Governance contract deployment failed"; exit 1; } +log INFO "Governance contract ID: $GOVERNANCE_CONTRACT_ID" -echo ">>> Initializing governance contract..." +log INFO "Initializing governance contract..." stellar contract invoke \ --id "$GOVERNANCE_CONTRACT_ID" \ --source "$STELLAR_SECRET_KEY" \ @@ -100,14 +182,17 @@ stellar contract invoke \ --voting_token "$TOKEN_CONTRACT_ID" \ --min_proposal_balance "$MIN_PROPOSAL_BALANCE" \ --proposal_cooldown "$PROPOSAL_COOLDOWN" \ - --restrict_admin_vote "$RESTRICT_ADMIN_VOTE" - -# ─── Write deployed addresses ──────────────────────────────────────────────── -echo "" -echo "=== Deployment complete ===" -echo "TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" -echo "GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" -echo "" -echo "Add these to your .env file:" -echo " TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" -echo " GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" + --restrict_admin_vote "$RESTRICT_ADMIN_VOTE" \ + || { log ERROR "Governance contract initialization failed"; exit 1; } + +# ─── Post-deployment verification ──────────────────────────────────────────── +verify_deployment "$TOKEN_CONTRACT_ID" "$GOVERNANCE_CONTRACT_ID" "$STELLAR_RPC_URL" "$PASSPHRASE" + +# ─── Summary ───────────────────────────────────────────────────────────────── +log INFO "=== Deployment complete ===" +log INFO "TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" +log INFO "GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" +log INFO "Add these to your .env file:" +log INFO " TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" +log INFO " GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" +log INFO "Full log saved to: $LOG_FILE" diff --git a/scripts/deploy_mainnet.sh b/scripts/deploy_mainnet.sh index da50e54..391f04c 100755 --- a/scripts/deploy_mainnet.sh +++ b/scripts/deploy_mainnet.sh @@ -5,15 +5,43 @@ # Double-check all parameters before running. set -euo pipefail +# ─── Logging ───────────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(dirname "$SCRIPT_DIR")" - +LOG_DIR="$ROOT_DIR/logs" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/deploy_mainnet_$(date +%Y%m%d_%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +log() { + local level="$1"; shift + echo "[$(date +%Y-%m-%dT%H:%M:%S)] [$level] $*" +} + +trap 'log ERROR "Script failed at line $LINENO. Check $LOG_FILE for details."' ERR + +# ─── Env validation ────────────────────────────────────────────────────────── +check_required_env() { + local missing=0 + for var in "$@"; do + if [[ -z "${!var:-}" ]]; then + log ERROR "Required environment variable '$var' is unset or empty" + missing=1 + fi + done + [[ $missing -eq 0 ]] || exit 1 +} + +CHECK_ENV_ONLY=false +[[ "${1:-}" == "--check-env" ]] && CHECK_ENV_ONLY=true + +# ─── Load environment ──────────────────────────────────────────────────────── if [[ -f "$ROOT_DIR/.env" ]]; then # shellcheck disable=SC1091 source "$ROOT_DIR/.env" fi -STELLAR_SECRET_KEY="${STELLAR_SECRET_KEY:?STELLAR_SECRET_KEY must be set}" +STELLAR_SECRET_KEY="${STELLAR_SECRET_KEY:-}" INITIAL_TOKEN_SUPPLY="${INITIAL_TOKEN_SUPPLY:-1000000000}" TOKEN_NAME="${TOKEN_NAME:-CosmosVote}" TOKEN_SYMBOL="${TOKEN_SYMBOL:-VOTE}" @@ -25,47 +53,105 @@ RESTRICT_ADMIN_VOTE="${RESTRICT_ADMIN_VOTE:-true}" PASSPHRASE="Public Global Stellar Network ; September 2015" RPC_URL="https://soroban-mainnet.stellar.org" -echo "╔══════════════════════════════════════════════════════════╗" -echo "║ CosmosVote — MAINNET DEPLOYMENT ║" -echo "╠══════════════════════════════════════════════════════════╣" -echo "║ ⚠️ You are about to deploy to STELLAR MAINNET ║" -echo "║ This action is IRREVERSIBLE. ║" -echo "╚══════════════════════════════════════════════════════════╝" -echo "" -echo "Parameters:" -echo " Initial supply : $INITIAL_TOKEN_SUPPLY" -echo " Min proposal balance: $MIN_PROPOSAL_BALANCE" -echo " Proposal cooldown : ${PROPOSAL_COOLDOWN}s" -echo " Restrict admin vote : $RESTRICT_ADMIN_VOTE" -echo "" +check_required_env STELLAR_SECRET_KEY + +if $CHECK_ENV_ONLY; then + log INFO "All required environment variables are set." + exit 0 +fi + +# ─── Backup & verification helpers ─────────────────────────────────────────── +BACKUP_DIR="$ROOT_DIR/logs/backups" +mkdir -p "$BACKUP_DIR" + +backup_contracts() { + local stamp; stamp="$(date +%Y%m%d_%H%M%S)" + local backup_file="$BACKUP_DIR/contracts_mainnet_${stamp}.env" + { + echo "# CosmosVote contract backup — $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "# Network: mainnet" + echo "TOKEN_CONTRACT_ID=${TOKEN_CONTRACT_ID:-}" + echo "GOVERNANCE_CONTRACT_ID=${GOVERNANCE_CONTRACT_ID:-}" + } > "$backup_file" + log INFO "Pre-deployment backup saved: $backup_file" + echo "$backup_file" +} + +verify_deployment() { + local token_id="$1" gov_id="$2" + log INFO "Verifying token contract ($token_id)..." + stellar contract invoke \ + --id "$token_id" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$RPC_URL" \ + --network-passphrase "$PASSPHRASE" \ + -- total_supply >/dev/null \ + || { log ERROR "Token contract verification failed"; return 1; } + + log INFO "Verifying governance contract ($gov_id)..." + stellar contract invoke \ + --id "$gov_id" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$RPC_URL" \ + --network-passphrase "$PASSPHRASE" \ + -- get_proposal_count >/dev/null \ + || { log ERROR "Governance contract verification failed"; return 1; } + + log INFO "Post-deployment verification passed." +} + +log WARN "╔══════════════════════════════════════════════════════════╗" +log WARN "║ CosmosVote — MAINNET DEPLOYMENT ║" +log WARN "╠══════════════════════════════════════════════════════════╣" +log WARN "║ ⚠️ You are about to deploy to STELLAR MAINNET ║" +log WARN "║ This action is IRREVERSIBLE. ║" +log WARN "╚══════════════════════════════════════════════════════════╝" +log INFO "Parameters:" +log INFO " Initial supply : $INITIAL_TOKEN_SUPPLY" +log INFO " Min proposal balance: $MIN_PROPOSAL_BALANCE" +log INFO " Proposal cooldown : ${PROPOSAL_COOLDOWN}s" +log INFO " Restrict admin vote : $RESTRICT_ADMIN_VOTE" +log INFO "Log file: $LOG_FILE" + read -r -p "Type 'deploy mainnet' to confirm: " CONFIRM if [[ "$CONFIRM" != "deploy mainnet" ]]; then - echo "Aborted." + log WARN "Deployment aborted by user." exit 1 fi -echo "" -echo ">>> Building WASM binaries..." +# ─── Pre-deployment backup ──────────────────────────────────────────────────── +backup_contracts + +# ─── Build ─────────────────────────────────────────────────────────────────── +log INFO "Building WASM binaries..." cd "$ROOT_DIR" -cargo build --release --target wasm32-unknown-unknown +cargo build --release --target wasm32-unknown-unknown \ + || { log ERROR "cargo build failed"; exit 1; } TOKEN_WASM="$ROOT_DIR/target/wasm32-unknown-unknown/release/cosmosvote_token.wasm" GOV_WASM="$ROOT_DIR/target/wasm32-unknown-unknown/release/cosmosvote_governance.wasm" -ADMIN_ADDRESS=$(stellar keys address --secret-key "$STELLAR_SECRET_KEY" 2>/dev/null || \ - stellar keys address "$STELLAR_SECRET_KEY") +for wasm in "$TOKEN_WASM" "$GOV_WASM"; do + [[ -f "$wasm" ]] || { log ERROR "WASM not found: $wasm"; exit 1; } +done -echo "Admin: $ADMIN_ADDRESS" +# ─── Derive admin address ──────────────────────────────────────────────────── +ADMIN_ADDRESS=$(stellar keys address --secret-key "$STELLAR_SECRET_KEY" 2>/dev/null || \ + stellar keys address "$STELLAR_SECRET_KEY") \ + || { log ERROR "Failed to derive admin address from STELLAR_SECRET_KEY"; exit 1; } +log INFO "Admin: $ADMIN_ADDRESS" -echo ">>> Deploying token contract to mainnet..." +# ─── Deploy token contract ─────────────────────────────────────────────────── +log INFO "Deploying token contract to mainnet..." TOKEN_CONTRACT_ID=$(stellar contract deploy \ --wasm "$TOKEN_WASM" \ --source "$STELLAR_SECRET_KEY" \ --rpc-url "$RPC_URL" \ - --network-passphrase "$PASSPHRASE") - -echo "Token contract ID: $TOKEN_CONTRACT_ID" + --network-passphrase "$PASSPHRASE") \ + || { log ERROR "Token contract deployment failed"; exit 1; } +log INFO "Token contract ID: $TOKEN_CONTRACT_ID" +log INFO "Initializing token contract..." stellar contract invoke \ --id "$TOKEN_CONTRACT_ID" \ --source "$STELLAR_SECRET_KEY" \ @@ -76,17 +162,20 @@ stellar contract invoke \ --initial_supply "$INITIAL_TOKEN_SUPPLY" \ --name "$TOKEN_NAME" \ --symbol "$TOKEN_SYMBOL" \ - --decimals "$TOKEN_DECIMALS" + --decimals "$TOKEN_DECIMALS" \ + || { log ERROR "Token contract initialization failed"; exit 1; } -echo ">>> Deploying governance contract to mainnet..." +# ─── Deploy governance contract ────────────────────────────────────────────── +log INFO "Deploying governance contract to mainnet..." GOVERNANCE_CONTRACT_ID=$(stellar contract deploy \ --wasm "$GOV_WASM" \ --source "$STELLAR_SECRET_KEY" \ --rpc-url "$RPC_URL" \ - --network-passphrase "$PASSPHRASE") - -echo "Governance contract ID: $GOVERNANCE_CONTRACT_ID" + --network-passphrase "$PASSPHRASE") \ + || { log ERROR "Governance contract deployment failed"; exit 1; } +log INFO "Governance contract ID: $GOVERNANCE_CONTRACT_ID" +log INFO "Initializing governance contract..." stellar contract invoke \ --id "$GOVERNANCE_CONTRACT_ID" \ --source "$STELLAR_SECRET_KEY" \ @@ -97,9 +186,14 @@ stellar contract invoke \ --voting_token "$TOKEN_CONTRACT_ID" \ --min_proposal_balance "$MIN_PROPOSAL_BALANCE" \ --proposal_cooldown "$PROPOSAL_COOLDOWN" \ - --restrict_admin_vote "$RESTRICT_ADMIN_VOTE" + --restrict_admin_vote "$RESTRICT_ADMIN_VOTE" \ + || { log ERROR "Governance contract initialization failed"; exit 1; } + +# ─── Post-deployment verification ──────────────────────────────────────────── +verify_deployment "$TOKEN_CONTRACT_ID" "$GOVERNANCE_CONTRACT_ID" -echo "" -echo "=== Mainnet deployment complete ===" -echo "TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" -echo "GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" +# ─── Summary ───────────────────────────────────────────────────────────────── +log INFO "=== Mainnet deployment complete ===" +log INFO "TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" +log INFO "GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" +log INFO "Full log saved to: $LOG_FILE" diff --git a/scripts/rollback.sh b/scripts/rollback.sh new file mode 100755 index 0000000..591ddb3 --- /dev/null +++ b/scripts/rollback.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# rollback.sh — Restore CosmosVote contract IDs from a pre-deployment backup. +# +# Usage: +# ./rollback.sh # list available backups +# ./rollback.sh # restore from specific backup +# ./rollback.sh --latest # restore from most recent backup +# +# What this script does: +# 1. Reads TOKEN_CONTRACT_ID and GOVERNANCE_CONTRACT_ID from the backup file. +# 2. Updates .env (or prints export commands if .env is absent). +# 3. Verifies the restored contracts still respond on-chain. +# +# NOTE: Soroban contract deployments are irreversible on-chain. This script +# restores the *references* (contract IDs) in your environment so your +# application points back to the previously known-good contracts. It does NOT +# undeploy or modify any on-chain state. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +BACKUP_DIR="$ROOT_DIR/logs/backups" +LOG_DIR="$ROOT_DIR/logs" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/rollback_$(date +%Y%m%d_%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +log() { + local level="$1"; shift + echo "[$(date +%Y-%m-%dT%H:%M:%S)] [$level] $*" +} + +trap 'log ERROR "Rollback failed at line $LINENO. Check $LOG_FILE for details."' ERR + +# ─── Load environment ──────────────────────────────────────────────────────── +if [[ -f "$ROOT_DIR/.env" ]]; then + # shellcheck disable=SC1091 + source "$ROOT_DIR/.env" +fi + +STELLAR_SECRET_KEY="${STELLAR_SECRET_KEY:-}" +NETWORK="${NETWORK:-local}" + +# ─── List backups helper ───────────────────────────────────────────────────── +list_backups() { + if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then + log WARN "No backups found in $BACKUP_DIR" + exit 0 + fi + log INFO "Available backups:" + ls -1t "$BACKUP_DIR"/*.env | while read -r f; do + echo " $f" + done +} + +# ─── Argument handling ─────────────────────────────────────────────────────── +ARG="${1:-}" + +if [[ -z "$ARG" ]]; then + list_backups + exit 0 +fi + +if [[ "$ARG" == "--latest" ]]; then + BACKUP_FILE=$(ls -1t "$BACKUP_DIR"/*.env 2>/dev/null | head -1) \ + || { log ERROR "No backup files found in $BACKUP_DIR"; exit 1; } + log INFO "Using latest backup: $BACKUP_FILE" +else + BACKUP_FILE="$ARG" +fi + +[[ -f "$BACKUP_FILE" ]] || { log ERROR "Backup file not found: $BACKUP_FILE"; exit 1; } + +# ─── Parse backup ──────────────────────────────────────────────────────────── +log INFO "Reading backup: $BACKUP_FILE" +cat "$BACKUP_FILE" + +# shellcheck disable=SC1090 +source "$BACKUP_FILE" + +[[ -n "${TOKEN_CONTRACT_ID:-}" ]] || { log ERROR "TOKEN_CONTRACT_ID missing in backup"; exit 1; } +[[ -n "${GOVERNANCE_CONTRACT_ID:-}" ]] || { log ERROR "GOVERNANCE_CONTRACT_ID missing in backup"; exit 1; } + +log INFO "Restoring:" +log INFO " TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" +log INFO " GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" + +# ─── Update .env ───────────────────────────────────────────────────────────── +ENV_FILE="$ROOT_DIR/.env" +if [[ -f "$ENV_FILE" ]]; then + # Update existing entries in-place; append if absent. + for var in TOKEN_CONTRACT_ID GOVERNANCE_CONTRACT_ID; do + val="${!var}" + if grep -q "^${var}=" "$ENV_FILE"; then + sed -i "s|^${var}=.*|${var}=${val}|" "$ENV_FILE" + else + echo "${var}=${val}" >> "$ENV_FILE" + fi + done + log INFO ".env updated." +else + log WARN ".env not found. Export these variables manually:" + echo " export TOKEN_CONTRACT_ID=$TOKEN_CONTRACT_ID" + echo " export GOVERNANCE_CONTRACT_ID=$GOVERNANCE_CONTRACT_ID" +fi + +# ─── Verify restored contracts ─────────────────────────────────────────────── +if [[ -z "$STELLAR_SECRET_KEY" ]]; then + log WARN "STELLAR_SECRET_KEY not set; skipping on-chain verification." + log INFO "Rollback complete (unverified). Set STELLAR_SECRET_KEY and re-run to verify." + exit 0 +fi + +case "$NETWORK" in + local) + RPC_URL="${STELLAR_RPC_URL:-http://localhost:8000}" + PASSPHRASE="${STELLAR_NETWORK_PASSPHRASE:-Standalone Network ; February 2021}" + ;; + testnet) + RPC_URL="https://soroban-testnet.stellar.org" + PASSPHRASE="Test SDF Network ; September 2015" + ;; + mainnet) + RPC_URL="https://soroban-mainnet.stellar.org" + PASSPHRASE="Public Global Stellar Network ; September 2015" + ;; + *) + log WARN "Unknown network '$NETWORK'; skipping on-chain verification." + exit 0 + ;; +esac + +log INFO "Verifying token contract ($TOKEN_CONTRACT_ID)..." +stellar contract invoke \ + --id "$TOKEN_CONTRACT_ID" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$RPC_URL" \ + --network-passphrase "$PASSPHRASE" \ + -- total_supply >/dev/null \ + || { log ERROR "Token contract ($TOKEN_CONTRACT_ID) did not respond. Verify the contract ID is correct for network '$NETWORK'."; exit 1; } + +log INFO "Verifying governance contract ($GOVERNANCE_CONTRACT_ID)..." +stellar contract invoke \ + --id "$GOVERNANCE_CONTRACT_ID" \ + --source "$STELLAR_SECRET_KEY" \ + --rpc-url "$RPC_URL" \ + --network-passphrase "$PASSPHRASE" \ + -- get_proposal_count >/dev/null \ + || { log ERROR "Governance contract ($GOVERNANCE_CONTRACT_ID) did not respond. Verify the contract ID is correct for network '$NETWORK'."; exit 1; } + +log INFO "=== Rollback complete and verified ===" +log INFO "Full log saved to: $LOG_FILE" diff --git a/scripts/test_wasm.sh b/scripts/test_wasm.sh index 8e18d86..2634fd0 100755 --- a/scripts/test_wasm.sh +++ b/scripts/test_wasm.sh @@ -2,40 +2,79 @@ # test_wasm.sh — Build and smoke-test WASM binaries. set -euo pipefail +# ─── Logging ───────────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(dirname "$SCRIPT_DIR")" +LOG_DIR="$ROOT_DIR/logs" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/test_wasm_$(date +%Y%m%d_%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 -echo "=== CosmosVote WASM Test ===" +log() { + local level="$1"; shift + echo "[$(date +%Y-%m-%dT%H:%M:%S)] [$level] $*" +} + +trap 'log ERROR "Script failed at line $LINENO. Check $LOG_FILE for details."' ERR + +# ─── Env validation ────────────────────────────────────────────────────────── +check_required_env() { + local missing=0 + for var in "$@"; do + if [[ -z "${!var:-}" ]]; then + log ERROR "Required environment variable '$var' is unset or empty" + missing=1 + fi + done + [[ $missing -eq 0 ]] || exit 1 +} + +CHECK_ENV_ONLY=false +[[ "${1:-}" == "--check-env" ]] && CHECK_ENV_ONLY=true + +# No required env vars for WASM build/test; validate toolchain presence instead. +if $CHECK_ENV_ONLY; then + command -v cargo &>/dev/null || { log ERROR "cargo is not installed or not in PATH"; exit 1; } + rustup target list --installed 2>/dev/null | grep -q "wasm32-unknown-unknown" \ + || { log ERROR "Rust target 'wasm32-unknown-unknown' is not installed. Run: rustup target add wasm32-unknown-unknown"; exit 1; } + log INFO "Toolchain check passed." + exit 0 +fi + +log INFO "=== CosmosVote WASM Test ===" +log INFO "Log file: $LOG_FILE" cd "$ROOT_DIR" -echo ">>> Building WASM binaries..." -cargo build --release --target wasm32-unknown-unknown +log INFO "Building WASM binaries..." +cargo build --release --target wasm32-unknown-unknown \ + || { log ERROR "cargo build failed"; exit 1; } TOKEN_WASM="$ROOT_DIR/target/wasm32-unknown-unknown/release/cosmosvote_token.wasm" GOV_WASM="$ROOT_DIR/target/wasm32-unknown-unknown/release/cosmosvote_governance.wasm" -echo "" -echo ">>> Checking WASM files exist..." +log INFO "Checking WASM files exist..." for wasm in "$TOKEN_WASM" "$GOV_WASM"; do if [[ -f "$wasm" ]]; then size=$(du -h "$wasm" | cut -f1) - echo " ✓ $(basename "$wasm") ($size)" + log INFO " ✓ $(basename "$wasm") ($size)" else - echo " ✗ $(basename "$wasm") NOT FOUND" >&2 + log ERROR " ✗ $(basename "$wasm") NOT FOUND" exit 1 fi done -echo "" -echo ">>> Inspecting WASM exports (requires wasm-objdump or stellar CLI)..." +log INFO "Inspecting WASM exports..." if command -v stellar &>/dev/null; then - echo "Token contract interface:" - stellar contract inspect --wasm "$TOKEN_WASM" 2>/dev/null || echo " (stellar CLI inspect not available)" - echo "" - echo "Governance contract interface:" - stellar contract inspect --wasm "$GOV_WASM" 2>/dev/null || echo " (stellar CLI inspect not available)" + log INFO "Token contract interface:" + stellar contract inspect --wasm "$TOKEN_WASM" 2>/dev/null \ + || log WARN "stellar CLI inspect not available for token" + log INFO "Governance contract interface:" + stellar contract inspect --wasm "$GOV_WASM" 2>/dev/null \ + || log WARN "stellar CLI inspect not available for governance" +else + log WARN "stellar CLI not found; skipping contract inspection" fi -echo "" -echo "=== WASM test passed ===" +log INFO "=== WASM test passed ===" +log INFO "Full log saved to: $LOG_FILE"