- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
275 lines
8.0 KiB
Bash
Executable File
275 lines
8.0 KiB
Bash
Executable File
#!/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
|