Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/rollback.md
Original file line number Diff line number Diff line change
@@ -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_<network>_<YYYYMMDD_HHMMSS>.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 "<passphrase>" \
-- total_supply

# Governance contract
stellar contract invoke \
--id "$GOVERNANCE_CONTRACT_ID" \
--source "$STELLAR_SECRET_KEY" \
--rpc-url "$STELLAR_RPC_URL" \
--network-passphrase "<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 |
155 changes: 120 additions & 35 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,45 @@
# 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"
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}"
Expand All @@ -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}"
Expand All @@ -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" \
Expand All @@ -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" \
Expand All @@ -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"
Loading