feat: add hybx omnl stack and gas pmm tooling
Some checks failed
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 37s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m50s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 2m19s
Validation / validate-genesis (push) Successful in 51s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 10s
CI/CD Pipeline / Solidity Contracts (push) Failing after 12m56s
Validation / validate-smart-contracts (push) Failing after 12s
CI/CD Pipeline / Security Scanning (push) Failing after 15m52s
Validation / validate-security (push) Failing after 10m59s
Validation / validate-documentation (push) Failing after 17s
Validate Token List / validate (push) Failing after 30s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 26s
Verify Deployment / Verify Deployment (push) Failing after 56s
Some checks failed
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 37s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m50s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 2m19s
Validation / validate-genesis (push) Successful in 51s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 10s
CI/CD Pipeline / Solidity Contracts (push) Failing after 12m56s
Validation / validate-smart-contracts (push) Failing after 12s
CI/CD Pipeline / Security Scanning (push) Failing after 15m52s
Validation / validate-security (push) Failing after 10m59s
Validation / validate-documentation (push) Failing after 17s
Validate Token List / validate (push) Failing after 30s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 26s
Verify Deployment / Verify Deployment (push) Failing after 56s
This commit is contained in:
404
scripts/deployment/build-gas-pmm-execution-bundle.sh
Executable file
404
scripts/deployment/build-gas-pmm-execution-bundle.sh
Executable file
@@ -0,0 +1,404 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
PROJECT_ROOT="$(cd "$REPO_ROOT/.." && pwd)"
|
||||
BUNDLE_JSON="$REPO_ROOT/config/gas-pmm-execution-bundle.json"
|
||||
OUT_DIR="$PROJECT_ROOT/reports/status"
|
||||
OUT_JSON="$OUT_DIR/gas-pmm-execution-bundle-latest.json"
|
||||
OUT_MD="$OUT_DIR/gas-pmm-execution-bundle-latest.md"
|
||||
|
||||
source "$REPO_ROOT/scripts/lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$REPO_ROOT"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
json_escape() {
|
||||
jq -Rn --arg v "${1:-}" '$v'
|
||||
}
|
||||
|
||||
resolve_env_from_list() {
|
||||
local joined="${1:-}"
|
||||
local value=""
|
||||
local key
|
||||
[[ -z "$joined" ]] && return 0
|
||||
IFS='|' read -r -a keys <<< "$joined"
|
||||
for key in "${keys[@]}"; do
|
||||
if [[ -n "${!key:-}" ]]; then
|
||||
value="${!key}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
hex_to_addr() {
|
||||
local value="${1#0x}"
|
||||
[[ ${#value} -ge 40 ]] || return 1
|
||||
printf '0x%s\n' "${value: -40}"
|
||||
}
|
||||
|
||||
code_status() {
|
||||
local rpc="$1"
|
||||
local address="$2"
|
||||
if [[ -z "$rpc" || -z "$address" ]]; then
|
||||
printf 'missing'
|
||||
return
|
||||
fi
|
||||
local code=""
|
||||
code="$(timeout 12s cast code --rpc-url "$rpc" "$address" 2>/dev/null || true)"
|
||||
if [[ -n "$code" && "$code" != "0x" ]]; then
|
||||
printf 'has_code'
|
||||
else
|
||||
printf 'no_code'
|
||||
fi
|
||||
}
|
||||
|
||||
symbol_call() {
|
||||
local rpc="$1"
|
||||
local address="$2"
|
||||
local value
|
||||
value="$(timeout 8s cast call --rpc-url "$rpc" "$address" "symbol()(string)" 2>/dev/null || true)"
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
normalize_key() {
|
||||
printf '%s' "$1" | tr '[:lower:]- ' '[:upper:]__'
|
||||
}
|
||||
|
||||
append_finding() {
|
||||
local finding="$1"
|
||||
if [[ -n "$FINDINGS_JOINED" ]]; then
|
||||
FINDINGS_JOINED+="|$finding"
|
||||
else
|
||||
FINDINGS_JOINED="$finding"
|
||||
fi
|
||||
}
|
||||
|
||||
emit_findings_json() {
|
||||
local joined="${1:-}"
|
||||
if [[ -z "$joined" ]]; then
|
||||
printf '[]'
|
||||
return
|
||||
fi
|
||||
jq -Rn --arg joined "$joined" '$joined | split("|") | map(select(length > 0))'
|
||||
}
|
||||
|
||||
rollout_order_json="$(jq -c '[.chains[] | {chainId, network}]' "$BUNDLE_JSON")"
|
||||
generated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
|
||||
json_file="$(mktemp)"
|
||||
md_file="$(mktemp)"
|
||||
|
||||
{
|
||||
echo "{"
|
||||
echo " \"generatedAt\": $(json_escape "$generated_at"),"
|
||||
echo " \"bundle\": $(jq '.bundleName' "$BUNDLE_JSON"),"
|
||||
echo " \"description\": $(jq '.description' "$BUNDLE_JSON"),"
|
||||
echo " \"poolDefaults\": $(jq '.poolDefaults' "$BUNDLE_JSON"),"
|
||||
echo " \"rolloutOrder\": $rollout_order_json,"
|
||||
echo " \"chains\": ["
|
||||
} >"$json_file"
|
||||
|
||||
{
|
||||
echo "# Gas PMM Execution Bundle"
|
||||
echo
|
||||
echo "Generated at: \`$generated_at\`"
|
||||
echo
|
||||
echo "This bundle covers the remaining \`22\` planned gas-family DODO PMM rows across \`11\` public chains."
|
||||
echo
|
||||
echo "Execution posture:"
|
||||
echo
|
||||
echo "- Each chain has two target pools: wrapped-native self-quote and wrapped-native versus stable."
|
||||
echo "- The current expected pool addresses are still scaffold slots, so \`no_code\` is the normal pre-deploy state."
|
||||
echo "- Stable-quote pool creation needs a fresh \`STABLE_PRICE_1E18\` input at execution time."
|
||||
echo "- Wrapped-native quote defaults in this bundle use the repo's canonical or documented live contracts instead of the old \`0xaa...\` placeholders."
|
||||
echo "- The deploy step reuses the legacy integration constructor slots; those seed addresses are only deployment anchors, while the gas pairs themselves are created through generic \`createPool(...)\` calls."
|
||||
} >"$md_file"
|
||||
|
||||
first_chain=1
|
||||
chain_count=0
|
||||
ready_count=0
|
||||
blocked_count=0
|
||||
|
||||
while IFS= read -r chain_row; do
|
||||
chain_id="$(jq -r '.chainId' <<<"$chain_row")"
|
||||
network="$(jq -r '.network' <<<"$chain_row")"
|
||||
chain_key="$(jq -r '.chainKey' <<<"$chain_row")"
|
||||
rpc_envs_joined="$(jq -r '.rpcEnv | join("|")' <<<"$chain_row")"
|
||||
integration_env="$(jq -r '.integrationEnv' <<<"$chain_row")"
|
||||
dvm_envs_joined="$(jq -r '.dodoVendingMachineEnv | join("|")' <<<"$chain_row")"
|
||||
|
||||
rpc="$(resolve_env_from_list "$rpc_envs_joined")"
|
||||
integration="${!integration_env:-}"
|
||||
dvm="$(resolve_env_from_list "$dvm_envs_joined")"
|
||||
|
||||
base_symbol="$(jq -r '.baseToken.symbol' <<<"$chain_row")"
|
||||
base_address="$(jq -r '.baseToken.address' <<<"$chain_row")"
|
||||
|
||||
wrapped_symbol="$(jq -r '.wrappedNativeQuote.symbol' <<<"$chain_row")"
|
||||
wrapped_envs_joined="$(jq -r '.wrappedNativeQuote.env | join("|")' <<<"$chain_row")"
|
||||
wrapped_default="$(jq -r '.wrappedNativeQuote.default' <<<"$chain_row")"
|
||||
wrapped_note="$(jq -r '.wrappedNativeQuote.note' <<<"$chain_row")"
|
||||
wrapped_address="$(resolve_env_from_list "$wrapped_envs_joined")"
|
||||
wrapped_address="${wrapped_address:-$wrapped_default}"
|
||||
|
||||
stable_symbol="$(jq -r '.stableQuote.symbol' <<<"$chain_row")"
|
||||
stable_envs_joined="$(jq -r '.stableQuote.env | join("|")' <<<"$chain_row")"
|
||||
stable_default="$(jq -r '.stableQuote.default' <<<"$chain_row")"
|
||||
stable_note="$(jq -r '.stableQuote.note' <<<"$chain_row")"
|
||||
stable_address="$(resolve_env_from_list "$stable_envs_joined")"
|
||||
stable_address="${stable_address:-$stable_default}"
|
||||
|
||||
integration_status="$(code_status "$rpc" "$integration")"
|
||||
dvm_status="$(code_status "$rpc" "$dvm")"
|
||||
base_status="$(code_status "$rpc" "$base_address")"
|
||||
wrapped_status="$(code_status "$rpc" "$wrapped_address")"
|
||||
stable_status="$(code_status "$rpc" "$stable_address")"
|
||||
|
||||
wrapped_pair_row="$(jq -c '.pairs[] | select(.quoteSymbol == $quote)' --arg quote "$wrapped_symbol" <<<"$chain_row")"
|
||||
stable_pair_row="$(jq -c '.pairs[] | select(.quoteSymbol == $quote)' --arg quote "$stable_symbol" <<<"$chain_row")"
|
||||
wrapped_pair_name="$(jq -r '.pair' <<<"$wrapped_pair_row")"
|
||||
stable_pair_name="$(jq -r '.pair' <<<"$stable_pair_row")"
|
||||
wrapped_pool_expected="$(jq -r '.expectedPoolAddress' <<<"$wrapped_pair_row")"
|
||||
stable_pool_expected="$(jq -r '.expectedPoolAddress' <<<"$stable_pair_row")"
|
||||
wrapped_pool_status="$(code_status "$rpc" "$wrapped_pool_expected")"
|
||||
stable_pool_status="$(code_status "$rpc" "$stable_pool_expected")"
|
||||
|
||||
FINDINGS_JOINED=""
|
||||
[[ -z "$rpc" ]] && append_finding "missing_rpc"
|
||||
[[ "$dvm_status" != "has_code" ]] && append_finding "missing_or_invalid_dodo_vending_machine"
|
||||
[[ "$base_status" != "has_code" ]] && append_finding "gas_mirror_base_missing_or_no_code"
|
||||
[[ "$wrapped_status" != "has_code" ]] && append_finding "wrapped_native_quote_missing_or_no_code"
|
||||
[[ "$stable_status" != "has_code" ]] && append_finding "stable_quote_missing_or_no_code"
|
||||
if [[ -n "$integration" && "$integration_status" == "no_code" ]]; then
|
||||
append_finding "integration_env_set_but_no_code"
|
||||
fi
|
||||
if [[ -z "$integration" || "$integration_status" != "has_code" ]]; then
|
||||
append_finding "integration_needs_deploy"
|
||||
fi
|
||||
[[ "$wrapped_pool_status" != "has_code" ]] && append_finding "wrapped_native_pool_not_live"
|
||||
[[ "$stable_pool_status" != "has_code" ]] && append_finding "stable_quote_pool_not_live"
|
||||
|
||||
readiness="blocked"
|
||||
if [[ -n "$rpc" && "$dvm_status" == "has_code" && "$base_status" == "has_code" && "$wrapped_status" == "has_code" && "$stable_status" == "has_code" ]]; then
|
||||
readiness="ready_for_execution"
|
||||
fi
|
||||
if [[ "$readiness" == "ready_for_execution" ]]; then
|
||||
ready_count=$((ready_count + 1))
|
||||
else
|
||||
blocked_count=$((blocked_count + 1))
|
||||
fi
|
||||
chain_count=$((chain_count + 1))
|
||||
|
||||
normalized_chain_key="$(normalize_key "$chain_key")"
|
||||
forge_profile="default"
|
||||
if [[ "$chain_id" == "25" ]]; then
|
||||
forge_profile="cronos_legacy"
|
||||
fi
|
||||
dvm_primary_env="$(jq -r '.dodoVendingMachineEnv[0]' <<<"$chain_row")"
|
||||
|
||||
deploy_cmd=$(cat <<EOF
|
||||
cd $REPO_ROOT
|
||||
DODO_VENDING_MACHINE_ADDRESS=\${$dvm_primary_env:-$dvm} \\
|
||||
OFFICIAL_USDT_ADDRESS=$stable_address \\
|
||||
OFFICIAL_USDC_ADDRESS=$wrapped_address \\
|
||||
COMPLIANT_USDT_ADDRESS=$base_address \\
|
||||
COMPLIANT_USDC_ADDRESS=$base_address \\
|
||||
FOUNDRY_PROFILE=$forge_profile \\
|
||||
forge script script/dex/DeployDODOPMMIntegration.s.sol:DeployDODOPMMIntegration \\
|
||||
--rpc-url $rpc \\
|
||||
--chain-id $chain_id \\
|
||||
--broadcast \\
|
||||
--private-key "\$PRIVATE_KEY" \\
|
||||
-vvv
|
||||
jq -r '.transactions[] | select(.transactionType=="CREATE" and .contractName=="DODOPMMIntegration") | .contractAddress' \\
|
||||
broadcast/DeployDODOPMMIntegration.s.sol/$chain_id/run-latest.json
|
||||
EOF
|
||||
)
|
||||
|
||||
create_cmd=$(cat <<EOF
|
||||
cd $REPO_ROOT
|
||||
INTEGRATION_ADDRESS=\${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION}
|
||||
LP_FEE_RATE=\${LP_FEE_RATE:-3}
|
||||
SELF_QUOTE_PRICE_1E18=1000000000000000000
|
||||
STABLE_PRICE_1E18=\${STABLE_PRICE_1E18:-REPLACE_WITH_LIVE_${normalized_chain_key}_GAS_PRICE_1E18}
|
||||
K_FACTOR=\${K_FACTOR:-500000000000000000}
|
||||
ENABLE_TWAP=\${ENABLE_TWAP:-true}
|
||||
|
||||
cast send "\$INTEGRATION_ADDRESS" \\
|
||||
"createPool(address,address,uint256,uint256,uint256,bool)" \\
|
||||
$base_address \\
|
||||
$wrapped_address \\
|
||||
"\$LP_FEE_RATE" \\
|
||||
"\$SELF_QUOTE_PRICE_1E18" \\
|
||||
"\$K_FACTOR" \\
|
||||
"\$ENABLE_TWAP" \\
|
||||
--rpc-url $rpc \\
|
||||
--private-key "\$PRIVATE_KEY" \\
|
||||
--legacy \\
|
||||
-vv
|
||||
|
||||
cast send "\$INTEGRATION_ADDRESS" \\
|
||||
"createPool(address,address,uint256,uint256,uint256,bool)" \\
|
||||
$base_address \\
|
||||
$stable_address \\
|
||||
"\$LP_FEE_RATE" \\
|
||||
"\$STABLE_PRICE_1E18" \\
|
||||
"\$K_FACTOR" \\
|
||||
"\$ENABLE_TWAP" \\
|
||||
--rpc-url $rpc \\
|
||||
--private-key "\$PRIVATE_KEY" \\
|
||||
--legacy \\
|
||||
-vv
|
||||
EOF
|
||||
)
|
||||
|
||||
verify_cmd=$(cat <<EOF
|
||||
cast code --rpc-url $rpc $wrapped_pool_expected
|
||||
cast code --rpc-url $rpc $stable_pool_expected
|
||||
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $base_address $wrapped_address
|
||||
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $base_address $stable_address
|
||||
EOF
|
||||
)
|
||||
|
||||
if [[ $first_chain -eq 0 ]]; then
|
||||
echo "," >>"$json_file"
|
||||
fi
|
||||
first_chain=0
|
||||
|
||||
cat >>"$json_file" <<EOF
|
||||
{
|
||||
"chainId": $chain_id,
|
||||
"network": $(json_escape "$network"),
|
||||
"chainKey": $(json_escape "$chain_key"),
|
||||
"rpc": $(json_escape "$rpc"),
|
||||
"readiness": $(json_escape "$readiness"),
|
||||
"blockingFindings": $(emit_findings_json "$FINDINGS_JOINED"),
|
||||
"integrationEnv": $(json_escape "$integration_env"),
|
||||
"integrationAddress": $(json_escape "$integration"),
|
||||
"integrationStatus": $(json_escape "$integration_status"),
|
||||
"dodoVendingMachine": {
|
||||
"envCandidates": $(jq '.dodoVendingMachineEnv' <<<"$chain_row"),
|
||||
"address": $(json_escape "$dvm"),
|
||||
"status": $(json_escape "$dvm_status")
|
||||
},
|
||||
"baseToken": {
|
||||
"symbol": $(json_escape "$base_symbol"),
|
||||
"address": $(json_escape "$base_address"),
|
||||
"status": $(json_escape "$base_status"),
|
||||
"symbolOnChain": $(json_escape "$(symbol_call "$rpc" "$base_address")")
|
||||
},
|
||||
"wrappedNativeQuote": {
|
||||
"symbol": $(json_escape "$wrapped_symbol"),
|
||||
"envCandidates": $(jq '.wrappedNativeQuote.env' <<<"$chain_row"),
|
||||
"address": $(json_escape "$wrapped_address"),
|
||||
"default": $(json_escape "$wrapped_default"),
|
||||
"status": $(json_escape "$wrapped_status"),
|
||||
"symbolOnChain": $(json_escape "$(symbol_call "$rpc" "$wrapped_address")"),
|
||||
"note": $(json_escape "$wrapped_note")
|
||||
},
|
||||
"stableQuote": {
|
||||
"symbol": $(json_escape "$stable_symbol"),
|
||||
"envCandidates": $(jq '.stableQuote.env' <<<"$chain_row"),
|
||||
"address": $(json_escape "$stable_address"),
|
||||
"default": $(json_escape "$stable_default"),
|
||||
"status": $(json_escape "$stable_status"),
|
||||
"symbolOnChain": $(json_escape "$(symbol_call "$rpc" "$stable_address")"),
|
||||
"note": $(json_escape "$stable_note")
|
||||
},
|
||||
"pairs": [
|
||||
{
|
||||
"pair": $(json_escape "$wrapped_pair_name"),
|
||||
"expectedPoolAddress": $(json_escape "$wrapped_pool_expected"),
|
||||
"poolCodeStatus": $(json_escape "$wrapped_pool_status"),
|
||||
"executionPriceMode": "self_quote_fixed_1e18"
|
||||
},
|
||||
{
|
||||
"pair": $(json_escape "$stable_pair_name"),
|
||||
"expectedPoolAddress": $(json_escape "$stable_pool_expected"),
|
||||
"poolCodeStatus": $(json_escape "$stable_pool_status"),
|
||||
"executionPriceMode": "live_stable_spot_required"
|
||||
}
|
||||
],
|
||||
"commands": {
|
||||
"deployIntegration": $(json_escape "$deploy_cmd"),
|
||||
"createPools": $(json_escape "$create_cmd"),
|
||||
"verifyPools": $(json_escape "$verify_cmd")
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
{
|
||||
echo
|
||||
echo "## $network ($chain_id)"
|
||||
echo
|
||||
echo "Readiness: \`$readiness\`"
|
||||
echo
|
||||
echo "| Item | Address | Status |"
|
||||
echo "| --- | --- | --- |"
|
||||
echo "| DODO vending machine | \`${dvm:-unset}\` | \`$dvm_status\` |"
|
||||
echo "| Integration env \`$integration_env\` | \`${integration:-unset}\` | \`$integration_status\` |"
|
||||
echo "| $base_symbol | \`$base_address\` | \`$base_status\` |"
|
||||
echo "| $wrapped_symbol | \`$wrapped_address\` | \`$wrapped_status\` |"
|
||||
echo "| $stable_symbol | \`$stable_address\` | \`$stable_status\` |"
|
||||
echo "| Pool $wrapped_pair_name | \`$wrapped_pool_expected\` | \`$wrapped_pool_status\` |"
|
||||
echo "| Pool $stable_pair_name | \`$stable_pool_expected\` | \`$stable_pool_status\` |"
|
||||
echo
|
||||
echo "Blocking findings:"
|
||||
if [[ -n "$FINDINGS_JOINED" ]]; then
|
||||
IFS='|' read -r -a findings_array <<< "$FINDINGS_JOINED"
|
||||
for finding in "${findings_array[@]}"; do
|
||||
echo "- \`$finding\`"
|
||||
done
|
||||
else
|
||||
echo "- None"
|
||||
fi
|
||||
echo
|
||||
echo "Quote token notes:"
|
||||
echo
|
||||
echo "- $wrapped_symbol: $wrapped_note"
|
||||
echo "- $stable_symbol: $stable_note"
|
||||
echo
|
||||
echo "Deploy integration:"
|
||||
echo
|
||||
echo '```bash'
|
||||
echo "$deploy_cmd"
|
||||
echo '```'
|
||||
echo
|
||||
echo "Create pools:"
|
||||
echo
|
||||
echo '```bash'
|
||||
echo "$create_cmd"
|
||||
echo '```'
|
||||
echo
|
||||
echo "Verify pools:"
|
||||
echo
|
||||
echo '```bash'
|
||||
echo "$verify_cmd"
|
||||
echo '```'
|
||||
} >>"$md_file"
|
||||
done < <(jq -c '.chains[]' "$BUNDLE_JSON")
|
||||
|
||||
{
|
||||
echo
|
||||
echo " ],"
|
||||
echo " \"summary\": {"
|
||||
echo " \"chains\": $chain_count,"
|
||||
echo " \"pairs\": $((chain_count * 2)),"
|
||||
echo " \"readyForExecution\": $ready_count,"
|
||||
echo " \"blocked\": $blocked_count"
|
||||
echo " }"
|
||||
echo "}"
|
||||
} >>"$json_file"
|
||||
|
||||
jq . "$json_file" >"$OUT_JSON"
|
||||
mv "$md_file" "$OUT_MD"
|
||||
rm -f "$json_file"
|
||||
|
||||
echo "Wrote:"
|
||||
echo " $OUT_JSON"
|
||||
echo " $OUT_MD"
|
||||
284
scripts/deployment/build-optimism-cronos-dodo-4-pools-bundle.sh
Executable file
284
scripts/deployment/build-optimism-cronos-dodo-4-pools-bundle.sh
Executable file
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
PROJECT_ROOT="$(cd "$REPO_ROOT/.." && pwd)"
|
||||
BUNDLE_JSON="$REPO_ROOT/config/optimism-cronos-dodo-4-pools-execution-bundle.json"
|
||||
OUT_DIR="$PROJECT_ROOT/reports/status"
|
||||
OUT_JSON="$OUT_DIR/optimism-cronos-dodo-4-pools-execution-bundle-latest.json"
|
||||
OUT_MD="$OUT_DIR/optimism-cronos-dodo-4-pools-execution-bundle-latest.md"
|
||||
|
||||
source "$REPO_ROOT/scripts/lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$REPO_ROOT"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
json_escape() {
|
||||
jq -Rn --arg v "${1:-}" '$v'
|
||||
}
|
||||
|
||||
resolve_env_from_list() {
|
||||
local joined="$1"
|
||||
local value=""
|
||||
local key
|
||||
IFS='|' read -r -a _keys <<< "$joined"
|
||||
for key in "${_keys[@]}"; do
|
||||
if [[ -n "${!key:-}" ]]; then
|
||||
value="${!key}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
code_status() {
|
||||
local rpc="$1"
|
||||
local address="$2"
|
||||
if [[ -z "$rpc" || -z "$address" ]]; then
|
||||
printf 'missing'
|
||||
return
|
||||
fi
|
||||
local code=""
|
||||
code="$(cast code --rpc-url "$rpc" "$address" 2>/dev/null || true)"
|
||||
if [[ -n "$code" && "$code" != "0x" ]]; then
|
||||
printf 'has_code'
|
||||
else
|
||||
printf 'no_code'
|
||||
fi
|
||||
}
|
||||
|
||||
symbol_call() {
|
||||
local rpc="$1"
|
||||
local address="$2"
|
||||
local value
|
||||
value="$(cast call --rpc-url "$rpc" "$address" "symbol()(string)" 2>/dev/null || true)"
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
json_file="$(mktemp)"
|
||||
md_file="$(mktemp)"
|
||||
|
||||
generated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
|
||||
{
|
||||
echo "{"
|
||||
echo " \"generatedAt\": $(json_escape "$generated_at"),"
|
||||
echo " \"bundle\": $(jq '.bundleName' "$BUNDLE_JSON"),"
|
||||
echo " \"chains\": ["
|
||||
} >"$json_file"
|
||||
|
||||
{
|
||||
echo "# Optimism/Cronos DODO 4-Pool Execution Bundle"
|
||||
echo
|
||||
echo "Generated at: \`$generated_at\`"
|
||||
echo
|
||||
echo "This bundle covers the four remaining \`configured_no_code\` DODO PMM pools:"
|
||||
echo
|
||||
echo "- Optimism: \`cWUSDC/USDC\`, \`cWUSDT/USDT\`"
|
||||
echo "- Cronos: \`cWUSDC/USDC\`, \`cWUSDT/USDT\`"
|
||||
echo
|
||||
echo "Execution posture:"
|
||||
echo
|
||||
echo "- DODO vending-machine addresses are present in env."
|
||||
echo "- Compliant \`cWUSDT\` / \`cWUSDC\` token contracts have bytecode on both chains."
|
||||
echo "- The expected pool addresses still have no code on both chains."
|
||||
echo "- The canonical quote anchors for Optimism/Cronos are the chain-native official USDT/USDC addresses below."
|
||||
} >"$md_file"
|
||||
|
||||
first_chain=1
|
||||
while IFS= read -r chain_row; do
|
||||
chain_id="$(jq -r '.chainId' <<<"$chain_row")"
|
||||
network="$(jq -r '.network' <<<"$chain_row")"
|
||||
integration_env="$(jq -r '.integrationEnv' <<<"$chain_row")"
|
||||
dvm_env="$(jq -r '.dodoVendingMachineEnv' <<<"$chain_row")"
|
||||
rpc_envs_joined="$(jq -r '.rpcEnv | join("|")' <<<"$chain_row")"
|
||||
|
||||
rpc="$(resolve_env_from_list "$rpc_envs_joined")"
|
||||
dvm="${!dvm_env:-}"
|
||||
integration="${!integration_env:-}"
|
||||
|
||||
official_usdc_env="$(jq -r '.official.USDC.env' <<<"$chain_row")"
|
||||
official_usdc_default="$(jq -r '.official.USDC.default' <<<"$chain_row")"
|
||||
official_usdt_env="$(jq -r '.official.USDT.env' <<<"$chain_row")"
|
||||
official_usdt_default="$(jq -r '.official.USDT.default' <<<"$chain_row")"
|
||||
official_usdc="${!official_usdc_env:-$official_usdc_default}"
|
||||
official_usdt="${!official_usdt_env:-$official_usdt_default}"
|
||||
|
||||
cwusdc_env="$(jq -r '.compliant.cWUSDC.env' <<<"$chain_row")"
|
||||
cwusdt_env="$(jq -r '.compliant.cWUSDT.env' <<<"$chain_row")"
|
||||
cwusdc="${!cwusdc_env:-}"
|
||||
cwusdt="${!cwusdt_env:-}"
|
||||
|
||||
integration_status="$(code_status "$rpc" "$integration")"
|
||||
dvm_status="$(code_status "$rpc" "$dvm")"
|
||||
official_usdc_status="$(code_status "$rpc" "$official_usdc")"
|
||||
official_usdt_status="$(code_status "$rpc" "$official_usdt")"
|
||||
cwusdc_status="$(code_status "$rpc" "$cwusdc")"
|
||||
cwusdt_status="$(code_status "$rpc" "$cwusdt")"
|
||||
|
||||
pool_usdc_expected="$(jq -r '.pairs[] | select(.pair=="cWUSDC/USDC") | .expectedPoolAddress' <<<"$chain_row")"
|
||||
pool_usdt_expected="$(jq -r '.pairs[] | select(.pair=="cWUSDT/USDT") | .expectedPoolAddress' <<<"$chain_row")"
|
||||
pool_usdc_status="$(code_status "$rpc" "$pool_usdc_expected")"
|
||||
pool_usdt_status="$(code_status "$rpc" "$pool_usdt_expected")"
|
||||
forge_profile="default"
|
||||
if [[ "$chain_id" == "25" ]]; then
|
||||
forge_profile="cronos_legacy"
|
||||
fi
|
||||
|
||||
deploy_cmd=$(cat <<EOF
|
||||
cd $REPO_ROOT
|
||||
DODO_VENDING_MACHINE_ADDRESS=$dvm \\
|
||||
OFFICIAL_USDT_ADDRESS=$official_usdt \\
|
||||
OFFICIAL_USDC_ADDRESS=$official_usdc \\
|
||||
COMPLIANT_USDT_ADDRESS=$cwusdt \\
|
||||
COMPLIANT_USDC_ADDRESS=$cwusdc \\
|
||||
FOUNDRY_PROFILE=$forge_profile \\
|
||||
forge script script/dex/DeployDODOPMMIntegration.s.sol:DeployDODOPMMIntegration \\
|
||||
--rpc-url $rpc \\
|
||||
--chain-id $chain_id \\
|
||||
--broadcast \\
|
||||
--private-key "\$PRIVATE_KEY" \\
|
||||
-vvv
|
||||
jq -r '.transactions[] | select(.transactionType=="CREATE" and .contractName=="DODOPMMIntegration") | .contractAddress' \\
|
||||
broadcast/DeployDODOPMMIntegration.s.sol/$chain_id/run-latest.json
|
||||
EOF
|
||||
)
|
||||
|
||||
create_cmd=$(cat <<EOF
|
||||
cd $REPO_ROOT
|
||||
DODO_PMM_INTEGRATION_ADDRESS=\${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} \\
|
||||
RPC_URL=$rpc \\
|
||||
COMPLIANT_USDT_ADDRESS=$cwusdt \\
|
||||
COMPLIANT_USDC_ADDRESS=$cwusdc \\
|
||||
OFFICIAL_USDT_ADDRESS=$official_usdt \\
|
||||
OFFICIAL_USDC_ADDRESS=$official_usdc \\
|
||||
PRIVATE_KEY="\$PRIVATE_KEY" \\
|
||||
bash scripts/setup-dodo-pools.sh
|
||||
EOF
|
||||
)
|
||||
|
||||
verify_cmd=$(cat <<EOF
|
||||
cast code --rpc-url $rpc $pool_usdc_expected
|
||||
cast code --rpc-url $rpc $pool_usdt_expected
|
||||
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $cwusdc $official_usdc
|
||||
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $cwusdt $official_usdt
|
||||
EOF
|
||||
)
|
||||
|
||||
if [[ $first_chain -eq 0 ]]; then
|
||||
echo "," >>"$json_file"
|
||||
fi
|
||||
first_chain=0
|
||||
|
||||
cat >>"$json_file" <<EOF
|
||||
{
|
||||
"chainId": $chain_id,
|
||||
"network": $(json_escape "$network"),
|
||||
"rpc": $(json_escape "$rpc"),
|
||||
"integrationEnv": $(json_escape "$integration_env"),
|
||||
"integrationAddress": $(json_escape "$integration"),
|
||||
"integrationStatus": $(json_escape "$integration_status"),
|
||||
"dodoVendingMachine": {
|
||||
"env": $(json_escape "$dvm_env"),
|
||||
"address": $(json_escape "$dvm"),
|
||||
"status": $(json_escape "$dvm_status")
|
||||
},
|
||||
"officialTokens": {
|
||||
"USDC": {
|
||||
"env": $(json_escape "$official_usdc_env"),
|
||||
"address": $(json_escape "$official_usdc"),
|
||||
"status": $(json_escape "$official_usdc_status"),
|
||||
"symbol": $(json_escape "$(symbol_call "$rpc" "$official_usdc")")
|
||||
},
|
||||
"USDT": {
|
||||
"env": $(json_escape "$official_usdt_env"),
|
||||
"address": $(json_escape "$official_usdt"),
|
||||
"status": $(json_escape "$official_usdt_status"),
|
||||
"symbol": $(json_escape "$(symbol_call "$rpc" "$official_usdt")")
|
||||
}
|
||||
},
|
||||
"compliantTokens": {
|
||||
"cWUSDC": {
|
||||
"env": $(json_escape "$cwusdc_env"),
|
||||
"address": $(json_escape "$cwusdc"),
|
||||
"status": $(json_escape "$cwusdc_status"),
|
||||
"symbol": $(json_escape "$(symbol_call "$rpc" "$cwusdc")")
|
||||
},
|
||||
"cWUSDT": {
|
||||
"env": $(json_escape "$cwusdt_env"),
|
||||
"address": $(json_escape "$cwusdt"),
|
||||
"status": $(json_escape "$cwusdt_status"),
|
||||
"symbol": $(json_escape "$(symbol_call "$rpc" "$cwusdt")")
|
||||
}
|
||||
},
|
||||
"pairs": [
|
||||
{
|
||||
"pair": "cWUSDC/USDC",
|
||||
"expectedPoolAddress": $(json_escape "$pool_usdc_expected"),
|
||||
"poolCodeStatus": $(json_escape "$pool_usdc_status")
|
||||
},
|
||||
{
|
||||
"pair": "cWUSDT/USDT",
|
||||
"expectedPoolAddress": $(json_escape "$pool_usdt_expected"),
|
||||
"poolCodeStatus": $(json_escape "$pool_usdt_status")
|
||||
}
|
||||
],
|
||||
"commands": {
|
||||
"deployIntegration": $(json_escape "$deploy_cmd"),
|
||||
"createPools": $(json_escape "$create_cmd"),
|
||||
"verifyPools": $(json_escape "$verify_cmd")
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
{
|
||||
echo
|
||||
echo "## $network ($chain_id)"
|
||||
echo
|
||||
echo "| Item | Address | Status |"
|
||||
echo "| --- | --- | --- |"
|
||||
echo "| DODO vending machine | \`$dvm\` | \`$dvm_status\` |"
|
||||
echo "| Integration env \`$integration_env\` | \`${integration:-unset}\` | \`$integration_status\` |"
|
||||
echo "| Official USDC | \`$official_usdc\` | \`$official_usdc_status\` |"
|
||||
echo "| Official USDT | \`$official_usdt\` | \`$official_usdt_status\` |"
|
||||
echo "| cWUSDC | \`$cwusdc\` | \`$cwusdc_status\` |"
|
||||
echo "| cWUSDT | \`$cwusdt\` | \`$cwusdt_status\` |"
|
||||
echo "| Pool cWUSDC/USDC | \`$pool_usdc_expected\` | \`$pool_usdc_status\` |"
|
||||
echo "| Pool cWUSDT/USDT | \`$pool_usdt_expected\` | \`$pool_usdt_status\` |"
|
||||
echo
|
||||
echo "Deploy integration:"
|
||||
echo
|
||||
echo '```bash'
|
||||
echo "$deploy_cmd"
|
||||
echo '```'
|
||||
echo
|
||||
echo "Create pools:"
|
||||
echo
|
||||
echo '```bash'
|
||||
echo "$create_cmd"
|
||||
echo '```'
|
||||
echo
|
||||
echo "Verify pools:"
|
||||
echo
|
||||
echo '```bash'
|
||||
echo "$verify_cmd"
|
||||
echo '```'
|
||||
} >>"$md_file"
|
||||
done < <(jq -c '.chains[]' "$BUNDLE_JSON")
|
||||
|
||||
{
|
||||
echo
|
||||
echo " ]"
|
||||
echo "}"
|
||||
} >>"$json_file"
|
||||
|
||||
jq . "$json_file" >"$OUT_JSON"
|
||||
mv "$md_file" "$OUT_MD"
|
||||
rm -f "$json_file"
|
||||
|
||||
echo "Wrote:"
|
||||
echo " $OUT_JSON"
|
||||
echo " $OUT_MD"
|
||||
232
scripts/deployment/c138-cw-bridge-mainnet-pct.sh
Executable file
232
scripts/deployment/c138-cw-bridge-mainnet-pct.sh
Executable file
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env bash
|
||||
# Chain 138 → Ethereum Mainnet only: bridge PCT of each canonical c* balance via CWMultiTokenBridgeL1.
|
||||
# Default PCT: 17.25% (= 1725 basis points of 10000; override with PCT_BP).
|
||||
#
|
||||
# Modes:
|
||||
# --plan-only Write JSON + summary (default)
|
||||
# --check-routes destinations(token, Mainnet selector) for each token
|
||||
# --emit-cmds Print approve + lockAndSend cast lines (review before running)
|
||||
#
|
||||
# Env: PRIVATE_KEY (for --emit-cmds deployer address), RPC_URL_138, CW_L1_BRIDGE_CHAIN138,
|
||||
# LINK_TOKEN_CHAIN138, RECIPIENT_ADDRESS (default: deployer)
|
||||
# PCT_BP (default 1725 = 17.25%)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
PROXMOX_ROOT="$(cd "$SMOM_ROOT/.." && pwd)"
|
||||
cd "$SMOM_ROOT"
|
||||
|
||||
MODE="plan"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--plan-only) MODE="plan" ;;
|
||||
--check-routes) MODE="check" ;;
|
||||
--emit-cmds) MODE="emit" ;;
|
||||
--help|-h)
|
||||
grep '^#' "$0" | head -22
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Unknown: $1"; exit 1 ;;
|
||||
esac
|
||||
shift || true
|
||||
done
|
||||
|
||||
if [[ -f "$PROXMOX_ROOT/scripts/lib/load-project-env.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
PROJECT_ROOT="$PROXMOX_ROOT" source "$PROXMOX_ROOT/scripts/lib/load-project-env.sh"
|
||||
elif [[ -f .env ]]; then
|
||||
set -a && source .env && set +a
|
||||
fi
|
||||
|
||||
PCT_BP="${PCT_BP:-1725}"
|
||||
RPC="${RPC_URL_138:-${CHAIN138_RPC:-${RPC_URL:-http://192.168.11.211:8545}}}"
|
||||
OUT_JSON="${OUT_JSON:-$SMOM_ROOT/reports/status/c138-bridge-mainnet-pct-latest.json}"
|
||||
BRIDGE="${CW_L1_BRIDGE_CHAIN138:-}"
|
||||
DEPLOYER=""
|
||||
if [[ -n "${PRIVATE_KEY:-}" ]]; then
|
||||
DEPLOYER="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)"
|
||||
fi
|
||||
RECIPIENT="${RECIPIENT_ADDRESS:-$DEPLOYER}"
|
||||
MAINNET_SEL=5009297550715157269
|
||||
|
||||
export RPC OUT_JSON DEPLOYER RECIPIENT BRIDGE PCT_BP MAINNET_SEL
|
||||
|
||||
# Canonical c* only (EXPLORER_TOKEN_LIST_CROSSCHECK §5)
|
||||
read -r -d '' TOKEN_ROWS << 'EOF' || true
|
||||
cUSDT:0x93E66202A11B1772E55407B32B44e5Cd8eda7f22
|
||||
cUSDC:0xf22258f57794CC8E06237084b353Ab30fFfa640b
|
||||
cEURC:0x8085961F9cF02b4d800A3c6d386D31da4B34266a
|
||||
cEURT:0xdf4b71c61E5912712C1Bdd451416B9aC26949d72
|
||||
cGBPC:0x003960f16D9d34F2e98d62723B6721Fb92074aD2
|
||||
cGBPT:0x350f54e4D23795f86A9c03988c7135357CCaD97c
|
||||
cAUDC:0xD51482e567c03899eecE3CAe8a058161FD56069D
|
||||
cJPYC:0xEe269e1226a334182aace90056EE4ee5Cc8A6770
|
||||
cCHFC:0x873990849DDa5117d7C644f0aF24370797C03885
|
||||
cCADC:0x54dBd40cF05e15906A2C21f600937e96787f5679
|
||||
cXAUC:0x290E52a8819A4fbD0714E517225429aA2B70EC6b
|
||||
cXAUT:0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E
|
||||
EOF
|
||||
export TOKEN_ROWS
|
||||
mkdir -p "$(dirname "$OUT_JSON")"
|
||||
|
||||
python3 << PY
|
||||
import json, subprocess, os, re
|
||||
|
||||
rpc = os.environ.get("RPC", "http://192.168.11.211:8545")
|
||||
deployer = os.environ.get("DEPLOYER", "")
|
||||
recipient = os.environ.get("RECIPIENT", deployer)
|
||||
bridge = os.environ.get("BRIDGE", "")
|
||||
pct_bp = int(os.environ.get("PCT_BP", "1725"))
|
||||
sel = int(os.environ.get("MAINNET_SEL", "5009297550715157269"))
|
||||
|
||||
rows = []
|
||||
for line in os.environ.get("TOKEN_ROWS", "").strip().split("\n"):
|
||||
if not line.strip():
|
||||
continue
|
||||
sym, addr = line.split(":", 1)
|
||||
rows.append((sym.strip(), addr.strip()))
|
||||
|
||||
def balance_of(addr):
|
||||
if not deployer:
|
||||
return None
|
||||
r = subprocess.run(
|
||||
["cast", "call", addr, "balanceOf(address)(uint256)", deployer, "--rpc-url", rpc],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if r.returncode != 0:
|
||||
return None
|
||||
m = re.match(r"^\s*(\d+)", r.stdout.strip())
|
||||
return int(m.group(1)) if m else None
|
||||
|
||||
plan = {
|
||||
"schema": "c138-bridge-mainnet-pct/v1",
|
||||
"rpc_url": rpc,
|
||||
"deployer": deployer,
|
||||
"recipient": recipient,
|
||||
"cw_l1_bridge": bridge,
|
||||
"destination": "Ethereum Mainnet",
|
||||
"chain_selector": str(sel),
|
||||
"pct_basis_points": pct_bp,
|
||||
"pct_human": f"{pct_bp / 100:.2f}%",
|
||||
"tokens": [],
|
||||
}
|
||||
|
||||
for sym, addr in rows:
|
||||
bal = balance_of(addr)
|
||||
if bal is None:
|
||||
plan["tokens"].append({"symbol": sym, "address": addr, "error": "balance_of_failed"})
|
||||
continue
|
||||
amt = bal * pct_bp // 10000
|
||||
plan["tokens"].append({
|
||||
"symbol": sym,
|
||||
"address": addr,
|
||||
"balance_wei": str(bal),
|
||||
"amount_to_bridge_wei": str(amt),
|
||||
})
|
||||
|
||||
path = os.environ.get("OUT_JSON", "")
|
||||
with open(path, "w") as f:
|
||||
json.dump(plan, f, indent=2)
|
||||
|
||||
print(json.dumps({"written": path, "tokens": len(plan["tokens"])}))
|
||||
PY
|
||||
|
||||
python3 - <<'PY'
|
||||
import json, os
|
||||
with open(os.environ["OUT_JSON"]) as f:
|
||||
p = json.load(f)
|
||||
print("\n=== c* → Mainnet only:", p.get("pct_human"), "of balance (integer base units) ===\n")
|
||||
print(f"Deployer: {p.get('deployer','?')}\nRecipient: {p.get('recipient','?')}\nBridge: {p.get('cw_l1_bridge') or '(unset)'}\n")
|
||||
for t in p["tokens"]:
|
||||
if "error" in t:
|
||||
print(f"{t['symbol']}: {t['error']}")
|
||||
continue
|
||||
sym = t["symbol"]
|
||||
amt = int(t["amount_to_bridge_wei"])
|
||||
if sym.startswith("cXAU"):
|
||||
print(f"{sym:<10} bridge: {amt / 1e6:,.6f} troy oz (wei={t['amount_to_bridge_wei']})")
|
||||
else:
|
||||
print(f"{sym:<10} bridge: {amt / 1e6:,.6f} tokens (wei={t['amount_to_bridge_wei']})")
|
||||
print("\nJSON:", os.environ["OUT_JSON"])
|
||||
PY
|
||||
|
||||
if [[ "$MODE" == "plan" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ -n "$BRIDGE" ]] || { echo "Set CW_L1_BRIDGE_CHAIN138"; exit 1; }
|
||||
code=$(cast code "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0x")
|
||||
[[ -n "$code" && "$code" != "0x" ]] || { echo "No contract at CW_L1_BRIDGE_CHAIN138=$BRIDGE"; exit 1; }
|
||||
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
echo ""
|
||||
echo "=== Mainnet route: $BRIDGE ==="
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
sym="${line%%:*}"
|
||||
addr="${line#*:}"
|
||||
dest=$(cast call "$BRIDGE" "destinations(address,uint64)(address,bool)" "$addr" "$MAINNET_SEL" --rpc-url "$RPC" 2>/dev/null || echo "ERR")
|
||||
echo "$sym ($addr): destinations(Mainnet)=$dest"
|
||||
done <<< "$TOKEN_ROWS"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "emit" ]]; then
|
||||
[[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY required for --emit-cmds"; exit 1; }
|
||||
[[ -n "$RECIPIENT" ]] || { echo "RECIPIENT_ADDRESS or deployer required"; exit 1; }
|
||||
LINK_TOKEN="${LINK_TOKEN_CHAIN138:-${LINK_TOKEN:-}}"
|
||||
[[ -n "$LINK_TOKEN" ]] || { echo "Set LINK_TOKEN or LINK_TOKEN_CHAIN138 for fee approval lines"; exit 1; }
|
||||
export LINK_TOKEN
|
||||
OUT_CAST="${OUT_CAST:-$SMOM_ROOT/reports/status/c138-bridge-mainnet-pct-cast-commands.sh}"
|
||||
export OUT_CAST
|
||||
{
|
||||
echo "#!/usr/bin/env bash"
|
||||
echo "# Generated: c138-cw-bridge-mainnet-pct.sh --emit-cmds"
|
||||
echo "# Review fee + reserve verifier. Fund LINK on deployer for CCIP fees."
|
||||
echo "set -euo pipefail"
|
||||
python3 <<'PY'
|
||||
import json, os, subprocess
|
||||
rpc = os.environ["RPC"]
|
||||
bridge = os.environ["BRIDGE"]
|
||||
recipient = os.environ["RECIPIENT"]
|
||||
link = os.environ["LINK_TOKEN"]
|
||||
sel = int(os.environ["MAINNET_SEL"])
|
||||
with open(os.environ["OUT_JSON"]) as f:
|
||||
plan = json.load(f)
|
||||
for t in plan["tokens"]:
|
||||
if "error" in t:
|
||||
continue
|
||||
amt = t.get("amount_to_bridge_wei", "0")
|
||||
if int(amt) == 0:
|
||||
continue
|
||||
sym, token = t["symbol"], t["address"]
|
||||
chk = subprocess.run(
|
||||
["cast", "call", bridge, "destinations(address,uint64)(address,bool)", token, str(sel), "--rpc-url", rpc],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if chk.returncode != 0 or "true" not in chk.stdout:
|
||||
print(f"# SKIP {sym}: destination Mainnet not enabled or query failed")
|
||||
print(f"# cast output: {chk.stdout.strip()}")
|
||||
continue
|
||||
fee = subprocess.run(
|
||||
["cast", "call", bridge,
|
||||
"calculateFee(address,uint64,address,uint256)(uint256)",
|
||||
token, str(sel), recipient, amt,
|
||||
"--rpc-url", rpc],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
fq = fee.stdout.strip().split()[0] if fee.returncode == 0 else "0"
|
||||
print("")
|
||||
print(f"# {sym} -> Mainnet amount={amt} fee_wei={fq}")
|
||||
print(f"cast send {link} \"approve(address,uint256)\" {bridge} {fq} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 120000")
|
||||
print(f"cast send {token} \"approve(address,uint256)\" {bridge} {amt} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 400000")
|
||||
print(f"cast send {bridge} \"lockAndSend(address,uint64,address,uint256)\" {token} {sel} {recipient} {amt} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 4000000")
|
||||
PY
|
||||
} > "$OUT_CAST"
|
||||
chmod +x "$OUT_CAST"
|
||||
echo "Wrote: $OUT_CAST"
|
||||
wc -l "$OUT_CAST"
|
||||
exit 0
|
||||
fi
|
||||
231
scripts/deployment/configure-cw-public-bridge-mesh.sh
Executable file
231
scripts/deployment/configure-cw-public-bridge-mesh.sh
Executable file
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
if [[ -f "$REPO_ROOT/scripts/load-env.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$REPO_ROOT/scripts/load-env.sh" >/dev/null
|
||||
elif [[ -f "$REPO_ROOT/../scripts/lib/load-project-env.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$REPO_ROOT/../scripts/lib/load-project-env.sh" >/dev/null
|
||||
fi
|
||||
|
||||
APPLY=false
|
||||
CHAIN_FILTER=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--apply) APPLY=true ;;
|
||||
--chain=*) CHAIN_FILTER="${1#*=}" ;;
|
||||
--chain)
|
||||
CHAIN_FILTER="${2:-}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown arg: $1" >&2
|
||||
echo "Usage: $0 [--apply] [--chain <chain_id>]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
declare -A CHAIN_NAME=(
|
||||
[1]="Ethereum Mainnet"
|
||||
[10]="Optimism"
|
||||
[25]="Cronos"
|
||||
[56]="BSC"
|
||||
[100]="Gnosis"
|
||||
[137]="Polygon"
|
||||
[8453]="Base"
|
||||
[42161]="Arbitrum"
|
||||
[42220]="Celo"
|
||||
[43114]="Avalanche"
|
||||
)
|
||||
|
||||
declare -A CHAIN_SELECTOR=(
|
||||
[1]="5009297550715157269"
|
||||
[10]="3734403246176062136"
|
||||
[25]="1456215246176062136"
|
||||
[56]="11344663589394136015"
|
||||
[100]="465200170687744372"
|
||||
[137]="4051577828743386545"
|
||||
[8453]="15971525489660198786"
|
||||
[42161]="4949039107694359620"
|
||||
[42220]="1346049177634351622"
|
||||
[43114]="6433500567565415381"
|
||||
)
|
||||
|
||||
rpc_for_chain() {
|
||||
case "$1" in
|
||||
1) echo "${MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-${ETHEREUM_RPC_URL:-}}}" ;;
|
||||
10) echo "${OPTIMISM_RPC_URL:-${OPTIMISM_MAINNET_RPC:-}}" ;;
|
||||
25) echo "${CRONOS_RPC_URL:-}" ;;
|
||||
56) echo "${BSC_RPC_URL:-${BSC_MAINNET_RPC:-}}" ;;
|
||||
100) echo "${GNOSIS_RPC_URL:-${GNOSIS_MAINNET_RPC:-${GNOSIS_RPC:-}}}" ;;
|
||||
137) echo "${POLYGON_RPC_URL:-${POLYGON_MAINNET_RPC:-}}" ;;
|
||||
8453) echo "${BASE_RPC_URL:-${BASE_MAINNET_RPC:-}}" ;;
|
||||
42161) echo "${ARBITRUM_RPC_URL:-${ARBITRUM_MAINNET_RPC:-}}" ;;
|
||||
42220) echo "${CELO_RPC_URL:-}" ;;
|
||||
43114) echo "${AVALANCHE_RPC_URL:-${AVALANCHE_MAINNET_RPC:-}}" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
bridge_for_chain() {
|
||||
case "$1" in
|
||||
1) echo "${CW_BRIDGE_MAINNET:-}" ;;
|
||||
10) echo "${CW_BRIDGE_OPTIMISM:-}" ;;
|
||||
25) echo "${CW_BRIDGE_CRONOS:-}" ;;
|
||||
56) echo "${CW_BRIDGE_BSC:-}" ;;
|
||||
100) echo "${CW_BRIDGE_GNOSIS:-}" ;;
|
||||
137) echo "${CW_BRIDGE_POLYGON:-}" ;;
|
||||
8453) echo "${CW_BRIDGE_BASE:-}" ;;
|
||||
42161) echo "${CW_BRIDGE_ARBITRUM:-}" ;;
|
||||
42220) echo "${CW_BRIDGE_CELO:-}" ;;
|
||||
43114) echo "${CW_BRIDGE_AVALANCHE:-}" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
probe_generation() {
|
||||
local bridge="$1" rpc="$2"
|
||||
if timeout 10 cast call "$bridge" 'sendRouter()(address)' --rpc-url "$rpc" >/dev/null 2>&1; then
|
||||
echo "new"
|
||||
return
|
||||
fi
|
||||
if timeout 10 cast call "$bridge" 'ccipRouter()(address)' --rpc-url "$rpc" >/dev/null 2>&1; then
|
||||
echo "old"
|
||||
return
|
||||
fi
|
||||
echo "unknown"
|
||||
}
|
||||
|
||||
old_has_key() {
|
||||
local bridge="$1" rpc="$2" key="$3"
|
||||
local destinations
|
||||
destinations="$(timeout 12 cast call "$bridge" 'getDestinationChains()(uint64[])' --rpc-url "$rpc" 2>/dev/null || true)"
|
||||
[[ -z "$destinations" ]] && return 1
|
||||
|
||||
DESTINATIONS_PAYLOAD="$destinations" python3 - "$key" <<'PY'
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
target = sys.argv[1]
|
||||
payload = os.environ.get("DESTINATIONS_PAYLOAD", "")
|
||||
numbers = re.findall(r'\d+', payload)
|
||||
sys.exit(0 if target in numbers else 1)
|
||||
PY
|
||||
}
|
||||
|
||||
new_read_destination() {
|
||||
local bridge="$1" rpc="$2" key="$3"
|
||||
timeout 12 cast call "$bridge" 'destinations(uint64)((address,bool))' "$key" --rpc-url "$rpc" 2>/dev/null || true
|
||||
}
|
||||
|
||||
normalize_addr() {
|
||||
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
new_matches() {
|
||||
local raw="$1" expected="$2"
|
||||
local lower_raw lower_expected
|
||||
lower_raw="$(normalize_addr "$raw")"
|
||||
lower_expected="$(normalize_addr "$expected")"
|
||||
[[ "$lower_raw" == *"$lower_expected"* && "$lower_raw" == *"true"* ]]
|
||||
}
|
||||
|
||||
send_tx() {
|
||||
local rpc="$1" bridge="$2" signature="$3"
|
||||
shift 3
|
||||
cast send "$bridge" "$signature" "$@" --rpc-url "$rpc" --private-key "$PRIVATE_KEY"
|
||||
}
|
||||
|
||||
configure_old() {
|
||||
local source_chain="$1" target_key="$2" target_bridge="$3"
|
||||
local rpc bridge op
|
||||
rpc="$(rpc_for_chain "$source_chain")"
|
||||
bridge="$(bridge_for_chain "$source_chain")"
|
||||
if old_has_key "$bridge" "$rpc" "$target_key"; then
|
||||
op="updateDestination(uint64,address)"
|
||||
else
|
||||
op="addDestination(uint64,address)"
|
||||
fi
|
||||
if [[ "$APPLY" == true ]]; then
|
||||
send_tx "$rpc" "$bridge" "$op" "$target_key" "$target_bridge"
|
||||
else
|
||||
echo "DRY-RUN old chain=$source_chain key=$target_key bridge=$target_bridge op=$op"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_new() {
|
||||
local source_chain="$1" target_key="$2" target_bridge="$3"
|
||||
local rpc bridge raw
|
||||
rpc="$(rpc_for_chain "$source_chain")"
|
||||
bridge="$(bridge_for_chain "$source_chain")"
|
||||
raw="$(new_read_destination "$bridge" "$rpc" "$target_key")"
|
||||
if new_matches "$raw" "$target_bridge"; then
|
||||
echo "SKIP new chain=$source_chain key=$target_key already=$raw"
|
||||
return
|
||||
fi
|
||||
if [[ "$APPLY" == true ]]; then
|
||||
send_tx "$rpc" "$bridge" 'configureDestination(uint64,address,bool)' "$target_key" "$target_bridge" true
|
||||
else
|
||||
echo "DRY-RUN new chain=$source_chain key=$target_key bridge=$target_bridge"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_key() {
|
||||
local source_chain="$1" target_key="$2" target_bridge="$3" generation="$4"
|
||||
case "$generation" in
|
||||
old) configure_old "$source_chain" "$target_key" "$target_bridge" ;;
|
||||
new) configure_new "$source_chain" "$target_key" "$target_bridge" ;;
|
||||
*)
|
||||
echo "ERROR unknown generation for chain $source_chain" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo "Public cW bridge mesh configuration"
|
||||
echo "Apply mode: $APPLY"
|
||||
echo
|
||||
|
||||
declare -A GENERATION=()
|
||||
|
||||
for chain_id in 1 10 25 56 100 137 8453 42161 42220 43114; do
|
||||
[[ -n "$CHAIN_FILTER" && "$CHAIN_FILTER" != "$chain_id" ]] && continue
|
||||
rpc="$(rpc_for_chain "$chain_id")"
|
||||
bridge="$(bridge_for_chain "$chain_id")"
|
||||
if [[ -z "$rpc" || -z "$bridge" ]]; then
|
||||
echo "WARN chain=$chain_id missing rpc or bridge env; skipping"
|
||||
continue
|
||||
fi
|
||||
generation="$(probe_generation "$bridge" "$rpc")"
|
||||
GENERATION["$chain_id"]="$generation"
|
||||
echo "chain=$chain_id name='${CHAIN_NAME[$chain_id]}' generation=$generation bridge=$bridge"
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
for source_chain in 1 10 25 56 100 137 8453 42161 42220 43114; do
|
||||
[[ -n "$CHAIN_FILTER" && "$CHAIN_FILTER" != "$source_chain" ]] && continue
|
||||
[[ -z "${GENERATION[$source_chain]:-}" ]] && continue
|
||||
for target_chain in 1 10 25 56 100 137 8453 42161 42220 43114; do
|
||||
[[ "$source_chain" == "$target_chain" ]] && continue
|
||||
target_bridge="$(bridge_for_chain "$target_chain")"
|
||||
[[ -z "$target_bridge" ]] && continue
|
||||
|
||||
# Outbound path on source uses the target CCIP selector.
|
||||
configure_key "$source_chain" "${CHAIN_SELECTOR[$target_chain]}" "$target_bridge" "${GENERATION[$source_chain]}"
|
||||
|
||||
# Inbound receive path on source uses the remote public chain id.
|
||||
configure_key "$source_chain" "$target_chain" "$target_bridge" "${GENERATION[$source_chain]}"
|
||||
done
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Done."
|
||||
81
scripts/deployment/create-uniswap-v3-gas-pool.sh
Executable file
81
scripts/deployment/create-uniswap-v3-gas-pool.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
FACTORY="${FACTORY:-${UNISWAP_V3_FACTORY:-}}"
|
||||
RPC_URL="${RPC_URL:-}"
|
||||
TOKEN_A="${TOKEN_A:-}"
|
||||
TOKEN_B="${TOKEN_B:-}"
|
||||
FEE="${FEE:-500}"
|
||||
EXECUTE="${EXECUTE:-0}"
|
||||
SQRT_PRICE_X96="${SQRT_PRICE_X96:-79228162514264337593543950336}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY:-}"
|
||||
|
||||
if [[ -z "$FACTORY" || -z "$RPC_URL" || -z "$TOKEN_A" || -z "$TOKEN_B" ]]; then
|
||||
echo "Required: FACTORY RPC_URL TOKEN_A TOKEN_B" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_pool() {
|
||||
cast call "$FACTORY" \
|
||||
"getPool(address,address,uint24)(address)" \
|
||||
"$TOKEN_A" "$TOKEN_B" "$FEE" \
|
||||
--rpc-url "$RPC_URL" 2>/dev/null || true
|
||||
}
|
||||
|
||||
pool="$(get_pool)"
|
||||
pool="${pool//$'\n'/}"
|
||||
|
||||
if [[ -z "$pool" || "$pool" == "0x0000000000000000000000000000000000000000" ]]; then
|
||||
echo "Pool does not exist yet for $TOKEN_A / $TOKEN_B fee $FEE"
|
||||
if [[ "$EXECUTE" != "1" ]]; then
|
||||
echo "Dry run: set EXECUTE=1 to call createPool on $FACTORY"
|
||||
exit 0
|
||||
fi
|
||||
if [[ -z "$PRIVATE_KEY" ]]; then
|
||||
echo "PRIVATE_KEY is required when EXECUTE=1" >&2
|
||||
exit 1
|
||||
fi
|
||||
cast send "$FACTORY" \
|
||||
"createPool(address,address,uint24)" \
|
||||
"$TOKEN_A" "$TOKEN_B" "$FEE" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
-vv
|
||||
pool="$(get_pool)"
|
||||
pool="${pool//$'\n'/}"
|
||||
fi
|
||||
|
||||
echo "Pool address: $pool"
|
||||
|
||||
slot0="$(cast call "$pool" "slot0()((uint160,int24,uint16,uint16,uint16,uint8,bool))" --rpc-url "$RPC_URL" 2>/dev/null || true)"
|
||||
slot0_no_ws="$(printf '%s' "$slot0" | tr -d '[:space:]')"
|
||||
|
||||
if [[ "$slot0_no_ws" == "(0,0,0,0,0,0,false)" || -z "$slot0_no_ws" ]]; then
|
||||
echo "Pool is not initialized"
|
||||
if [[ "$EXECUTE" != "1" ]]; then
|
||||
echo "Dry run: set EXECUTE=1 to initialize at sqrtPriceX96=$SQRT_PRICE_X96"
|
||||
exit 0
|
||||
fi
|
||||
if [[ -z "$PRIVATE_KEY" ]]; then
|
||||
echo "PRIVATE_KEY is required when EXECUTE=1" >&2
|
||||
exit 1
|
||||
fi
|
||||
cast send "$pool" \
|
||||
"initialize(uint160)" \
|
||||
"$SQRT_PRICE_X96" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
-vv
|
||||
echo "Initialized pool at sqrtPriceX96=$SQRT_PRICE_X96"
|
||||
else
|
||||
echo "Pool already initialized: $slot0"
|
||||
fi
|
||||
299
scripts/deployment/cw-enforce-bridge-only-roles.sh
Executable file
299
scripts/deployment/cw-enforce-bridge-only-roles.sh
Executable file
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env bash
|
||||
# Reconcile cW* token roles so only the configured per-network bridge keeps MINTER/BURNER.
|
||||
#
|
||||
# The script is intentionally conservative:
|
||||
# - It grants MINTER/BURNER to the configured bridge if missing
|
||||
# - It revokes MINTER/BURNER from the deployer/admin by default
|
||||
# - It can revoke MINTER/BURNER from an explicit denylist of extra addresses
|
||||
# - It can optionally freeze future operational role changes on newer cW* contracts
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/deployment/cw-enforce-bridge-only-roles.sh --dry-run
|
||||
# bash scripts/deployment/cw-enforce-bridge-only-roles.sh
|
||||
#
|
||||
# Optional env:
|
||||
# CW_ROLE_CHAINS="1 10 25 56 100 137 42161 42220 43114 8453"
|
||||
# CW_ROLE_TOKENS="CWUSDT CWUSDC CWEURC"
|
||||
# CW_ROLE_EXTRA_REVOKE="0xabc...,0xdef..."
|
||||
# CW_ROLE_KEEP_ALLOWLIST="0xabc...,0xdef..."
|
||||
# CW_ROLE_REVOKE_DEPLOYER=1 # default 1
|
||||
# CW_ROLE_FREEZE_IF_SUPPORTED=1 # default 1
|
||||
# CW_ROLE_FROM_BLOCK=earliest # optional log scan start block for role event discovery
|
||||
# PRIVATE_KEY # required unless --dry-run
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
PROXMOX_ROOT="$(cd "$SMOM_ROOT/.." && pwd)"
|
||||
cd "$SMOM_ROOT"
|
||||
|
||||
DRY_RUN=0
|
||||
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||
DRY_RUN=1
|
||||
fi
|
||||
|
||||
if [[ -f "$PROXMOX_ROOT/scripts/lib/load-project-env.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
PROJECT_ROOT="$PROXMOX_ROOT" source "$PROXMOX_ROOT/scripts/lib/load-project-env.sh"
|
||||
elif [[ -f .env ]]; then
|
||||
set -a && source .env && set +a
|
||||
fi
|
||||
|
||||
if ! command -v cast >/dev/null 2>&1; then
|
||||
echo "cast is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "jq is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CHAIN_ROWS=(
|
||||
"1|MAINNET|ETHEREUM_MAINNET_RPC|CW_BRIDGE_MAINNET"
|
||||
"10|OPTIMISM|OPTIMISM_MAINNET_RPC|CW_BRIDGE_OPTIMISM"
|
||||
"25|CRONOS|CRONOS_MAINNET_RPC|CW_BRIDGE_CRONOS"
|
||||
"56|BSC|BSC_MAINNET_RPC|CW_BRIDGE_BSC"
|
||||
"100|GNOSIS|GNOSIS_MAINNET_RPC|CW_BRIDGE_GNOSIS"
|
||||
"137|POLYGON|POLYGON_MAINNET_RPC|CW_BRIDGE_POLYGON"
|
||||
"42161|ARBITRUM|ARBITRUM_MAINNET_RPC|CW_BRIDGE_ARBITRUM"
|
||||
"42220|CELO|CELO_MAINNET_RPC|CW_BRIDGE_CELO"
|
||||
"43114|AVALANCHE|AVALANCHE_MAINNET_RPC|CW_BRIDGE_AVALANCHE"
|
||||
"8453|BASE|BASE_MAINNET_RPC|CW_BRIDGE_BASE"
|
||||
)
|
||||
|
||||
TOKENS=(
|
||||
"CWUSDT"
|
||||
"CWUSDC"
|
||||
"CWAUSDT"
|
||||
"CWUSDW"
|
||||
"CWEURC"
|
||||
"CWEURT"
|
||||
"CWGBPC"
|
||||
"CWGBPT"
|
||||
"CWAUDC"
|
||||
"CWJPYC"
|
||||
"CWCHFC"
|
||||
"CWCADC"
|
||||
"CWXAUC"
|
||||
"CWXAUT"
|
||||
)
|
||||
|
||||
CHAIN_FILTER="${CW_ROLE_CHAINS:-1 10 25 56 100 137 42161 42220 43114 8453}"
|
||||
TOKEN_FILTER="${CW_ROLE_TOKENS:-}"
|
||||
REVOKE_DEPLOYER="${CW_ROLE_REVOKE_DEPLOYER:-1}"
|
||||
FREEZE_IF_SUPPORTED="${CW_ROLE_FREEZE_IF_SUPPORTED:-1}"
|
||||
EXTRA_REVOKE_RAW="${CW_ROLE_EXTRA_REVOKE:-}"
|
||||
KEEP_ALLOWLIST_RAW="${CW_ROLE_KEEP_ALLOWLIST:-}"
|
||||
ROLE_FROM_BLOCK="${CW_ROLE_FROM_BLOCK:-earliest}"
|
||||
|
||||
MINTER_ROLE="$(cast keccak "MINTER_ROLE")"
|
||||
BURNER_ROLE="$(cast keccak "BURNER_ROLE")"
|
||||
DEFAULT_ADMIN_ROLE="0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
send_cmd() {
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
local rendered=()
|
||||
local redact_next=0
|
||||
local arg
|
||||
for arg in "$@"; do
|
||||
if [[ "$redact_next" -eq 1 ]]; then
|
||||
rendered+=("<redacted>")
|
||||
redact_next=0
|
||||
continue
|
||||
fi
|
||||
if [[ "$arg" == "--private-key" ]]; then
|
||||
rendered+=("$arg")
|
||||
redact_next=1
|
||||
continue
|
||||
fi
|
||||
rendered+=("$arg")
|
||||
done
|
||||
echo "[dry-run] ${rendered[*]}"
|
||||
return 0
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
bool_call() {
|
||||
local rpc="$1"
|
||||
local contract="$2"
|
||||
local sig="$3"
|
||||
shift 3
|
||||
cast call "$contract" "$sig" "$@" --rpc-url "$rpc" 2>/dev/null
|
||||
}
|
||||
|
||||
normalize_address() {
|
||||
local value="${1,,}"
|
||||
value="${value#0x}"
|
||||
if [[ ${#value} -lt 40 ]]; then
|
||||
return 1
|
||||
fi
|
||||
echo "0x${value: -40}"
|
||||
}
|
||||
|
||||
append_unique_address() {
|
||||
local __var_name="$1"
|
||||
local candidate="${2,,}"
|
||||
[[ -n "$candidate" ]] || return 0
|
||||
local current="${!__var_name:-}"
|
||||
if [[ " $current " != *" $candidate "* ]]; then
|
||||
printf -v "$__var_name" '%s%s ' "$current" "$candidate"
|
||||
fi
|
||||
}
|
||||
|
||||
load_csv_addresses() {
|
||||
local raw="$1"
|
||||
local __out_var="$2"
|
||||
local item normalized
|
||||
IFS=',' read -r -a _addr_items <<< "$raw"
|
||||
for item in "${_addr_items[@]}"; do
|
||||
item="${item//[[:space:]]/}"
|
||||
[[ -n "$item" ]] || continue
|
||||
normalized="$(normalize_address "$item" || true)"
|
||||
[[ -n "$normalized" ]] || continue
|
||||
append_unique_address "$__out_var" "$normalized"
|
||||
done
|
||||
}
|
||||
|
||||
discover_role_holders() {
|
||||
local rpc="$1"
|
||||
local token="$2"
|
||||
local role="$3"
|
||||
cast logs "RoleGranted(bytes32,address,address)" "$role" \
|
||||
--from-block "$ROLE_FROM_BLOCK" --to-block latest --address "$token" --rpc-url "$rpc" --json 2>/dev/null |
|
||||
jq -r '.[].topics[2] // empty' |
|
||||
while read -r topic; do
|
||||
normalize_address "$topic" || true
|
||||
done
|
||||
}
|
||||
|
||||
function_exists() {
|
||||
local rpc="$1"
|
||||
local contract="$2"
|
||||
local sig="$3"
|
||||
cast call "$contract" "$sig" --rpc-url "$rpc" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
grant_role_if_supported() {
|
||||
local rpc="$1"
|
||||
local token="$2"
|
||||
local role="$3"
|
||||
local target="$4"
|
||||
local current
|
||||
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$target" || true)"
|
||||
if [[ "$current" != "true" ]]; then
|
||||
send_cmd cast send "$token" "grantRole(bytes32,address)" "$role" "$target" \
|
||||
--rpc-url "$rpc" --private-key "$PRIVATE_KEY"
|
||||
fi
|
||||
}
|
||||
|
||||
revoke_role_if_present() {
|
||||
local rpc="$1"
|
||||
local token="$2"
|
||||
local role="$3"
|
||||
local target="$4"
|
||||
local current
|
||||
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$target" || true)"
|
||||
if [[ "$current" == "true" ]]; then
|
||||
send_cmd cast send "$token" "revokeRole(bytes32,address)" "$role" "$target" \
|
||||
--rpc-url "$rpc" --private-key "$PRIVATE_KEY"
|
||||
fi
|
||||
}
|
||||
|
||||
role_label() {
|
||||
case "$1" in
|
||||
"$DEFAULT_ADMIN_ROLE") echo "DEFAULT_ADMIN_ROLE" ;;
|
||||
"$MINTER_ROLE") echo "MINTER_ROLE" ;;
|
||||
"$BURNER_ROLE") echo "BURNER_ROLE" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
for row in "${CHAIN_ROWS[@]}"; do
|
||||
IFS='|' read -r chain_id chain_key rpc_var bridge_var <<< "$row"
|
||||
if [[ " $CHAIN_FILTER " != *" $chain_id "* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
rpc="${!rpc_var:-}"
|
||||
bridge="${!bridge_var:-}"
|
||||
deployer="${DEPLOYER_ADDRESS:-${DEPLOYER_WALLET:-${DEPLOYER_EOA:-${DEFAULT_FROM_ADDRESS:-}}}}"
|
||||
if [[ -z "$deployer" && -n "${PRIVATE_KEY:-}" ]]; then
|
||||
deployer="$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true)"
|
||||
fi
|
||||
bridge="$(normalize_address "$bridge" || true)"
|
||||
deployer="$(normalize_address "$deployer" || true)"
|
||||
|
||||
if [[ -z "$rpc" || -z "$bridge" ]]; then
|
||||
echo "Skip chain $chain_id ($chain_key): missing rpc or bridge env"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "=== Chain $chain_id ($chain_key) ==="
|
||||
echo "Bridge: $bridge"
|
||||
|
||||
for token_prefix in "${TOKENS[@]}"; do
|
||||
if [[ -n "$TOKEN_FILTER" && " $TOKEN_FILTER " != *" $token_prefix "* ]]; then
|
||||
continue
|
||||
fi
|
||||
token_var="${token_prefix}_${chain_key}"
|
||||
token="${!token_var:-}"
|
||||
if [[ -z "$token" || "$token" == "0x0000000000000000000000000000000000000000" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "-- $token_var $token"
|
||||
|
||||
grant_role_if_supported "$rpc" "$token" "$MINTER_ROLE" "$bridge"
|
||||
grant_role_if_supported "$rpc" "$token" "$BURNER_ROLE" "$bridge"
|
||||
|
||||
keep_allowlist=""
|
||||
append_unique_address keep_allowlist "$bridge"
|
||||
load_csv_addresses "$KEEP_ALLOWLIST_RAW" keep_allowlist
|
||||
|
||||
revoke_candidates=""
|
||||
while read -r discovered; do
|
||||
[[ -n "$discovered" ]] || continue
|
||||
append_unique_address revoke_candidates "$discovered"
|
||||
done < <(discover_role_holders "$rpc" "$token" "$MINTER_ROLE")
|
||||
while read -r discovered; do
|
||||
[[ -n "$discovered" ]] || continue
|
||||
append_unique_address revoke_candidates "$discovered"
|
||||
done < <(discover_role_holders "$rpc" "$token" "$BURNER_ROLE")
|
||||
|
||||
if [[ "$REVOKE_DEPLOYER" == "1" && -n "$deployer" ]]; then
|
||||
append_unique_address revoke_candidates "$deployer"
|
||||
fi
|
||||
load_csv_addresses "$EXTRA_REVOKE_RAW" revoke_candidates
|
||||
|
||||
for addr in $revoke_candidates; do
|
||||
[[ " $keep_allowlist " == *" ${addr,,} "* ]] && continue
|
||||
revoke_role_if_present "$rpc" "$token" "$MINTER_ROLE" "$addr"
|
||||
revoke_role_if_present "$rpc" "$token" "$BURNER_ROLE" "$addr"
|
||||
done
|
||||
|
||||
if [[ "$FREEZE_IF_SUPPORTED" == "1" ]]; then
|
||||
if function_exists "$rpc" "$token" "operationalRolesFrozen()(bool)"; then
|
||||
frozen="$(cast call "$token" "operationalRolesFrozen()(bool)" --rpc-url "$rpc" 2>/dev/null || true)"
|
||||
if [[ "$frozen" != "true" ]]; then
|
||||
send_cmd cast send "$token" "freezeOperationalRoles()" \
|
||||
--rpc-url "$rpc" --private-key "$PRIVATE_KEY" --gas-limit 80000
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
for role in "$DEFAULT_ADMIN_ROLE" "$MINTER_ROLE" "$BURNER_ROLE"; do
|
||||
summary_holders=""
|
||||
while read -r discovered; do
|
||||
[[ -n "$discovered" ]] || continue
|
||||
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$discovered" || true)"
|
||||
if [[ "$current" == "true" ]]; then
|
||||
append_unique_address summary_holders "$discovered"
|
||||
fi
|
||||
done < <(discover_role_holders "$rpc" "$token" "$role")
|
||||
echo " $(role_label "$role"): ${summary_holders:-<none discovered>}"
|
||||
done
|
||||
done
|
||||
done
|
||||
54
scripts/deployment/deploy-chain138-gas-canonicals.sh
Normal file
54
scripts/deployment/deploy-chain138-gas-canonicals.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC:-http://192.168.11.211:8545}}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY:-}"
|
||||
GAS_PRICE="${GAS_PRICE:-1000000000}"
|
||||
EXECUTE="${EXECUTE:-0}"
|
||||
ONLY_FAMILY="${GAS_FAMILY:-${1:-}}"
|
||||
|
||||
if [[ -z "$PRIVATE_KEY" ]]; then
|
||||
echo "PRIVATE_KEY is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmd=(
|
||||
bash scripts/forge/scope.sh script tokens
|
||||
script/deploy/DeployGasCanonicalTokens.s.sol:DeployGasCanonicalTokens
|
||||
--rpc-url "$RPC_URL"
|
||||
--broadcast
|
||||
--private-key "$PRIVATE_KEY"
|
||||
--legacy
|
||||
--with-gas-price "$GAS_PRICE"
|
||||
-vvv
|
||||
)
|
||||
|
||||
export FOUNDRY_PROFILE="${FOUNDRY_PROFILE:-chain138_legacy}"
|
||||
if [[ -n "$ONLY_FAMILY" ]]; then
|
||||
export GAS_FAMILY="$ONLY_FAMILY"
|
||||
fi
|
||||
|
||||
echo "Chain 138 gas-canonical deploy"
|
||||
echo " profile: $FOUNDRY_PROFILE"
|
||||
echo " rpc: $RPC_URL"
|
||||
echo " gas price: $GAS_PRICE"
|
||||
echo " family: ${GAS_FAMILY:-all}"
|
||||
|
||||
if [[ "$EXECUTE" != "1" ]]; then
|
||||
echo "Dry run only. Re-run with EXECUTE=1 to broadcast."
|
||||
printf ' %q' "${cmd[@]}"
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
"${cmd[@]}"
|
||||
297
scripts/deployment/fund-chain138-eth-pmm-pools.sh
Normal file
297
scripts/deployment/fund-chain138-eth-pmm-pools.sh
Normal file
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$SMOM_ROOT"
|
||||
|
||||
if [[ -f "$SMOM_ROOT/scripts/lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source "$SMOM_ROOT/scripts/lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$SMOM_ROOT"
|
||||
else
|
||||
source "$SMOM_ROOT/scripts/load-env.sh" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
PLAN_JSON="${PLAN_JSON:-$SMOM_ROOT/config/chain138-eth-pmm-liquidity-plan.json}"
|
||||
PROFILE="${PROFILE:-}"
|
||||
EXECUTE="${EXECUTE:-0}"
|
||||
RPC_URL_138="${RPC_URL_138:-${RPC_URL:-http://192.168.11.211:8545}}"
|
||||
DODO_PMM_INTEGRATION_ADDRESS="${DODO_PMM_INTEGRATION_ADDRESS:-${DODO_PMM_INTEGRATION:-}}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY:-}"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2; exit 1; }
|
||||
command -v cast >/dev/null 2>&1 || { echo "cast is required" >&2; exit 1; }
|
||||
|
||||
[[ -f "$PLAN_JSON" ]] || { echo "PLAN_JSON not found: $PLAN_JSON" >&2; exit 1; }
|
||||
[[ -n "$DODO_PMM_INTEGRATION_ADDRESS" ]] || { echo "DODO_PMM_INTEGRATION_ADDRESS not set" >&2; exit 1; }
|
||||
require_private_key_env || exit 1
|
||||
|
||||
EXECUTION_CONFIG="$(jq -r '.executionConfig' "$PLAN_JSON")"
|
||||
if [[ "$EXECUTION_CONFIG" != /* ]]; then
|
||||
EXECUTION_CONFIG="$SMOM_ROOT/${EXECUTION_CONFIG#smom-dbis-138/}"
|
||||
fi
|
||||
[[ -f "$EXECUTION_CONFIG" ]] || { echo "Execution config not found: $EXECUTION_CONFIG" >&2; exit 1; }
|
||||
|
||||
if [[ -z "$PROFILE" ]]; then
|
||||
PROFILE="$(jq -r '.defaultProfile' "$PLAN_JSON")"
|
||||
fi
|
||||
|
||||
FIAT_BASE_UNITS="$(jq -r --arg p "$PROFILE" '.profiles[$p].fiatBaseUnits' "$PLAN_JSON")"
|
||||
XAU_BASE_UNITS="$(jq -r --arg p "$PROFILE" '.profiles[$p].xauBaseUnits' "$PLAN_JSON")"
|
||||
[[ "$FIAT_BASE_UNITS" != "null" ]] || { echo "Unknown PROFILE: $PROFILE" >&2; exit 1; }
|
||||
[[ "$XAU_BASE_UNITS" != "null" ]] || { echo "Unknown PROFILE: $PROFILE" >&2; exit 1; }
|
||||
|
||||
APPROVE_GAS_LIMIT="$(jq -r '.execution.approveGasLimit' "$PLAN_JSON")"
|
||||
MINT_GAS_LIMIT="$(jq -r '.execution.mintGasLimit' "$PLAN_JSON")"
|
||||
WRAP_GAS_LIMIT="$(jq -r '.execution.wrapGasLimit' "$PLAN_JSON")"
|
||||
ADD_LIQUIDITY_GAS_LIMIT="$(jq -r '.execution.addLiquidityGasLimit' "$PLAN_JSON")"
|
||||
GAS_PRICE_WEI="${CHAIN_GAS_PRICE:-$(jq -r '.execution.legacyGasPriceWei' "$PLAN_JSON")}"
|
||||
WRAP_WHEN_NEEDED="$(jq -r '.execution.wrapNativeEthWhenNeeded' "$PLAN_JSON")"
|
||||
MINT_WHEN_OWNER="$(jq -r '.execution.mintMissingBaseWhenOwner' "$PLAN_JSON")"
|
||||
APPROVE_MAX="$(jq -r '.execution.approveMax' "$PLAN_JSON")"
|
||||
|
||||
DEPLOYER="$(derive_deployer_address || true)"
|
||||
[[ -n "$DEPLOYER" ]] || { echo "ERROR: Could not derive DEPLOYER_ADDRESS from PRIVATE_KEY." >&2; exit 1; }
|
||||
WETH_ADDRESS="$(jq -r '.tokens.WETH' "$EXECUTION_CONFIG")"
|
||||
MAX_UINT="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
ZERO_ADDR="0x0000000000000000000000000000000000000000"
|
||||
|
||||
log() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
get_pool_reserves() {
|
||||
local pool="$1"
|
||||
local lines
|
||||
mapfile -t lines < <(cast call "$pool" 'getVaultReserve()(uint256,uint256)' --rpc-url "$RPC_URL_138")
|
||||
printf '%s\t%s\n' "$(uint_clean "${lines[0]:-0}")" "$(uint_clean "${lines[1]:-0}")"
|
||||
}
|
||||
|
||||
uint_clean() {
|
||||
printf '%s\n' "$1" | awk '{print $1}'
|
||||
}
|
||||
|
||||
hex_to_addr() {
|
||||
local raw="${1#0x}"
|
||||
if [[ ${#raw} -lt 40 ]]; then
|
||||
printf '%s\n' "$ZERO_ADDR"
|
||||
return 0
|
||||
fi
|
||||
printf '0x%s\n' "${raw: -40}"
|
||||
}
|
||||
|
||||
send_tx() {
|
||||
local to="$1"
|
||||
shift
|
||||
cast send "$to" "$@" \
|
||||
--rpc-url "$RPC_URL_138" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy \
|
||||
--gas-price "$GAS_PRICE_WEI" \
|
||||
-q
|
||||
}
|
||||
|
||||
big_add() {
|
||||
python3 - "$1" "$2" <<'PY'
|
||||
import sys
|
||||
print(int(sys.argv[1]) + int(sys.argv[2]))
|
||||
PY
|
||||
}
|
||||
|
||||
big_sub_if_positive() {
|
||||
python3 - "$1" "$2" <<'PY'
|
||||
import sys
|
||||
a = int(sys.argv[1])
|
||||
b = int(sys.argv[2])
|
||||
print(a - b if a > b else 0)
|
||||
PY
|
||||
}
|
||||
|
||||
big_lt() {
|
||||
python3 - "$1" "$2" <<'PY'
|
||||
import sys
|
||||
print("1" if int(sys.argv[1]) < int(sys.argv[2]) else "0")
|
||||
PY
|
||||
}
|
||||
|
||||
raw_to_human_6() {
|
||||
awk -v v="$1" 'BEGIN { printf "%.6f", v / 1000000 }'
|
||||
}
|
||||
|
||||
raw_to_human_18() {
|
||||
awk -v v="$1" 'BEGIN { printf "%.18f", v / 1000000000000000000 }'
|
||||
}
|
||||
|
||||
mul_div_round() {
|
||||
awk -v a="$1" -v b="$2" -v c="$3" 'BEGIN { printf "%.0f", (a * b) / c }'
|
||||
}
|
||||
|
||||
derive_usd_per_eth() {
|
||||
local pool base_reserve quote_reserve
|
||||
local cusdt quote
|
||||
cusdt="$(jq -r '.tokens.cUSDT' "$EXECUTION_CONFIG")"
|
||||
quote="$WETH_ADDRESS"
|
||||
pool="$(hex_to_addr "$(cast call "$DODO_PMM_INTEGRATION_ADDRESS" 'pools(address,address)(address)' "$cusdt" "$quote" --rpc-url "$RPC_URL_138")")"
|
||||
[[ "$pool" != "$ZERO_ADDR" ]] || { echo "0"; return 1; }
|
||||
read -r base_reserve quote_reserve < <(get_pool_reserves "$pool")
|
||||
awk -v base="$base_reserve" -v quote="$quote_reserve" 'BEGIN { printf "%.12f", (base / 1000000) / (quote / 1000000000000000000) }'
|
||||
}
|
||||
|
||||
derive_xau_usd_per_unit() {
|
||||
local pool base_symbol quote_symbol base_addr quote_addr base_reserve quote_reserve fallback
|
||||
base_symbol="$(jq -r '.xauPricing.poolBaseSymbol' "$PLAN_JSON")"
|
||||
quote_symbol="$(jq -r '.xauPricing.poolQuoteSymbol' "$PLAN_JSON")"
|
||||
fallback="$(jq -r '.xauPricing.fallbackUsdPerUnit' "$PLAN_JSON")"
|
||||
base_addr="$(jq -r --arg sym "$base_symbol" '.tokens[$sym]' "$EXECUTION_CONFIG")"
|
||||
quote_addr="$(jq -r --arg sym "$quote_symbol" '.tokens[$sym]' "$EXECUTION_CONFIG")"
|
||||
pool="$(hex_to_addr "$(cast call "$DODO_PMM_INTEGRATION_ADDRESS" 'pools(address,address)(address)' "$base_addr" "$quote_addr" --rpc-url "$RPC_URL_138")")"
|
||||
if [[ "$pool" == "$ZERO_ADDR" ]]; then
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
read -r base_reserve quote_reserve < <(get_pool_reserves "$pool")
|
||||
if [[ "$base_reserve" == "0" || "$quote_reserve" == "0" ]]; then
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
awk -v base="$base_reserve" -v quote="$quote_reserve" 'BEGIN { printf "%.12f", (quote / 1000000) / (base / 1000000) }'
|
||||
}
|
||||
|
||||
usd_per_eth="$(derive_usd_per_eth)"
|
||||
[[ "$usd_per_eth" != "0" ]] || { echo "Could not derive USD per ETH from live cUSDT/WETH pool" >&2; exit 1; }
|
||||
xau_usd_per_unit="$(derive_xau_usd_per_unit)"
|
||||
|
||||
log "=== Chain 138 ETH PMM Liquidity Funding ==="
|
||||
log "Mode: $( [[ "$EXECUTE" == "1" ]] && echo EXECUTE || echo DRY_RUN )"
|
||||
log "Profile: $PROFILE"
|
||||
log "Plan: $PLAN_JSON"
|
||||
log "Config: $EXECUTION_CONFIG"
|
||||
log "Integration: $DODO_PMM_INTEGRATION_ADDRESS"
|
||||
log "RPC: $RPC_URL_138"
|
||||
log "Deployer: $DEPLOYER"
|
||||
log "USD per ETH: $usd_per_eth"
|
||||
log "XAU USD: $xau_usd_per_unit"
|
||||
log ""
|
||||
|
||||
total_quote_units=0
|
||||
wrap_needed=0
|
||||
|
||||
declare -a ROWS=()
|
||||
while IFS=$'\t' read -r base_symbol base_address; do
|
||||
pool="$(hex_to_addr "$(cast call "$DODO_PMM_INTEGRATION_ADDRESS" 'pools(address,address)(address)' "$base_address" "$WETH_ADDRESS" --rpc-url "$RPC_URL_138")")"
|
||||
[[ "$pool" != "$ZERO_ADDR" ]] || continue
|
||||
|
||||
case "$base_symbol" in
|
||||
cXAUC|cXAUT)
|
||||
base_units="$XAU_BASE_UNITS"
|
||||
usd_per_unit="$xau_usd_per_unit"
|
||||
;;
|
||||
*)
|
||||
base_units="$FIAT_BASE_UNITS"
|
||||
usd_per_unit="$(jq -r --arg sym "$base_symbol" '.usdPerUnit[$sym]' "$PLAN_JSON")"
|
||||
[[ "$usd_per_unit" != "null" ]] || { echo "Missing usdPerUnit for $base_symbol" >&2; exit 1; }
|
||||
;;
|
||||
esac
|
||||
|
||||
quote_units="$(awk -v base="$base_units" -v usd="$usd_per_unit" -v usd_eth="$usd_per_eth" 'BEGIN { printf "%.0f", ((base / 1000000) * usd / usd_eth) * 1000000000000000000 }')"
|
||||
read -r current_base_reserve current_quote_reserve < <(get_pool_reserves "$pool")
|
||||
base_delta="$(big_sub_if_positive "$base_units" "$current_base_reserve")"
|
||||
quote_delta="$(big_sub_if_positive "$quote_units" "$current_quote_reserve")"
|
||||
base_balance="$(cast call "$base_address" 'balanceOf(address)(uint256)' "$DEPLOYER" --rpc-url "$RPC_URL_138" 2>/dev/null || echo 0)"
|
||||
base_balance="$(uint_clean "$base_balance")"
|
||||
owner_addr="$(cast call "$base_address" 'owner()(address)' --rpc-url "$RPC_URL_138" 2>/dev/null || echo "$ZERO_ADDR")"
|
||||
ROWS+=("$base_symbol|$base_address|$pool|$base_units|$quote_units|$current_base_reserve|$current_quote_reserve|$base_delta|$quote_delta|$base_balance|$owner_addr")
|
||||
total_quote_units="$(big_add "$total_quote_units" "$quote_delta")"
|
||||
done < <(jq -r '.explicitPairs[] | [.baseSymbol, (.baseSymbol as $s | .baseSymbol)] | @tsv' "$EXECUTION_CONFIG" | while IFS=$'\t' read -r base_symbol _; do
|
||||
base_address="$(jq -r --arg sym "$base_symbol" '.tokens[$sym]' "$EXECUTION_CONFIG")"
|
||||
printf '%s\t%s\n' "$base_symbol" "$base_address"
|
||||
done)
|
||||
|
||||
weth_balance="$(cast call "$WETH_ADDRESS" 'balanceOf(address)(uint256)' "$DEPLOYER" --rpc-url "$RPC_URL_138" 2>/dev/null || echo 0)"
|
||||
weth_balance="$(uint_clean "$weth_balance")"
|
||||
eth_balance="$(cast balance "$DEPLOYER" --rpc-url "$RPC_URL_138" 2>/dev/null || echo 0)"
|
||||
if [[ "$(big_lt "$weth_balance" "$total_quote_units")" == "1" ]]; then
|
||||
wrap_needed="$(big_sub_if_positive "$total_quote_units" "$weth_balance")"
|
||||
fi
|
||||
|
||||
log "Current ETH: $(raw_to_human_18 "$eth_balance")"
|
||||
log "Current WETH: $(raw_to_human_18 "$weth_balance")"
|
||||
log "Total WETH needed for profile: $(raw_to_human_18 "$total_quote_units")"
|
||||
log "Additional WETH to wrap: $(raw_to_human_18 "$wrap_needed")"
|
||||
log ""
|
||||
|
||||
for row in "${ROWS[@]}"; do
|
||||
IFS='|' read -r base_symbol base_address pool base_units quote_units current_base_reserve current_quote_reserve base_delta quote_delta base_balance owner_addr <<< "$row"
|
||||
need_mint=0
|
||||
if [[ "$(big_lt "$base_balance" "$base_delta")" == "1" ]]; then
|
||||
need_mint="$(big_sub_if_positive "$base_delta" "$base_balance")"
|
||||
fi
|
||||
log "$base_symbol pool=$pool"
|
||||
log " target base: $(raw_to_human_6 "$base_units")"
|
||||
log " target quote: $(raw_to_human_18 "$quote_units") WETH"
|
||||
log " current base: $(raw_to_human_6 "$current_base_reserve")"
|
||||
log " current quote: $(raw_to_human_18 "$current_quote_reserve") WETH"
|
||||
log " top-up base: $(raw_to_human_6 "$base_delta")"
|
||||
log " top-up quote: $(raw_to_human_18 "$quote_delta") WETH"
|
||||
log " base balance: $(raw_to_human_6 "$base_balance")"
|
||||
if [[ "$need_mint" != "0" ]]; then
|
||||
log " mint needed: $(raw_to_human_6 "$need_mint")"
|
||||
else
|
||||
log " mint needed: no"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$EXECUTE" != "1" ]]; then
|
||||
log ""
|
||||
log "Dry run only. Re-run with EXECUTE=1 to mint/wrap/approve/add liquidity."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$wrap_needed" != "0" ]]; then
|
||||
if [[ "$WRAP_WHEN_NEEDED" != "true" ]]; then
|
||||
echo "Need additional WETH but wrapNativeEthWhenNeeded=false" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(big_lt "$eth_balance" "$wrap_needed")" == "1" ]]; then
|
||||
echo "Insufficient ETH to wrap required WETH" >&2
|
||||
exit 1
|
||||
fi
|
||||
log "Wrapping $(raw_to_human_18 "$wrap_needed") ETH into WETH"
|
||||
send_tx "$WETH_ADDRESS" 'deposit()' --value "$wrap_needed" --gas-limit "$WRAP_GAS_LIMIT" >/dev/null
|
||||
fi
|
||||
|
||||
for row in "${ROWS[@]}"; do
|
||||
IFS='|' read -r base_symbol base_address pool base_units quote_units current_base_reserve current_quote_reserve base_delta quote_delta base_balance owner_addr <<< "$row"
|
||||
if [[ "$base_delta" == "0" && "$quote_delta" == "0" ]]; then
|
||||
log "Skipping $base_symbol/WETH; already at or above target."
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$(big_lt "$base_balance" "$base_delta")" == "1" ]]; then
|
||||
mint_amount="$(big_sub_if_positive "$base_delta" "$base_balance")"
|
||||
if [[ "$MINT_WHEN_OWNER" != "true" ]]; then
|
||||
echo "Need to mint $base_symbol but mintMissingBaseWhenOwner=false" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${owner_addr,,}" != "${DEPLOYER,,}" ]]; then
|
||||
echo "Cannot mint $base_symbol; deployer is not owner ($owner_addr)" >&2
|
||||
exit 1
|
||||
fi
|
||||
log "Minting $(raw_to_human_6 "$mint_amount") $base_symbol"
|
||||
send_tx "$base_address" 'mint(address,uint256)' "$DEPLOYER" "$mint_amount" --gas-limit "$MINT_GAS_LIMIT" >/dev/null
|
||||
fi
|
||||
|
||||
if [[ "$APPROVE_MAX" == "true" ]]; then
|
||||
log "Approving $base_symbol to integration"
|
||||
send_tx "$base_address" 'approve(address,uint256)' "$DODO_PMM_INTEGRATION_ADDRESS" "$MAX_UINT" --gas-limit "$APPROVE_GAS_LIMIT" >/dev/null
|
||||
log "Approving WETH for $base_symbol pair"
|
||||
send_tx "$WETH_ADDRESS" 'approve(address,uint256)' "$DODO_PMM_INTEGRATION_ADDRESS" "$MAX_UINT" --gas-limit "$APPROVE_GAS_LIMIT" >/dev/null
|
||||
fi
|
||||
|
||||
log "Adding liquidity to $base_symbol/WETH at $pool"
|
||||
send_tx "$DODO_PMM_INTEGRATION_ADDRESS" 'addLiquidity(address,uint256,uint256)' "$pool" "$base_delta" "$quote_delta" --gas-limit "$ADD_LIQUIDITY_GAS_LIMIT" >/dev/null
|
||||
done
|
||||
|
||||
log ""
|
||||
log "Liquidity funding complete."
|
||||
112
scripts/deployment/fund-uniswap-v3-gas-pool.sh
Executable file
112
scripts/deployment/fund-uniswap-v3-gas-pool.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
POSITION_MANAGER="${POSITION_MANAGER:-}"
|
||||
RPC_URL="${RPC_URL:-}"
|
||||
TOKEN_A="${TOKEN_A:-}"
|
||||
TOKEN_B="${TOKEN_B:-}"
|
||||
AMOUNT_A="${AMOUNT_A:-}"
|
||||
AMOUNT_B="${AMOUNT_B:-}"
|
||||
FEE="${FEE:-500}"
|
||||
EXECUTE="${EXECUTE:-0}"
|
||||
DEADLINE_SECONDS="${DEADLINE_SECONDS:-3600}"
|
||||
RECIPIENT="${RECIPIENT:-}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY:-}"
|
||||
|
||||
if [[ -z "$POSITION_MANAGER" || -z "$RPC_URL" || -z "$TOKEN_A" || -z "$TOKEN_B" || -z "$AMOUNT_A" || -z "$AMOUNT_B" ]]; then
|
||||
echo "Required: POSITION_MANAGER RPC_URL TOKEN_A TOKEN_B AMOUNT_A AMOUNT_B" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$RECIPIENT" ]]; then
|
||||
if [[ -z "$PRIVATE_KEY" ]]; then
|
||||
echo "Set RECIPIENT or PRIVATE_KEY" >&2
|
||||
exit 1
|
||||
fi
|
||||
RECIPIENT="$(cast wallet address --private-key "$PRIVATE_KEY")"
|
||||
fi
|
||||
|
||||
lower_hex="$(printf '%s\n%s\n' "$TOKEN_A" "$TOKEN_B" | tr '[:upper:]' '[:lower:]' | sort | sed -n '1p')"
|
||||
upper_hex="$(printf '%s\n%s\n' "$TOKEN_A" "$TOKEN_B" | tr '[:upper:]' '[:lower:]' | sort | sed -n '2p')"
|
||||
|
||||
token0="$lower_hex"
|
||||
token1="$upper_hex"
|
||||
amount0="$AMOUNT_A"
|
||||
amount1="$AMOUNT_B"
|
||||
|
||||
if [[ "${TOKEN_A,,}" != "$token0" ]]; then
|
||||
amount0="$AMOUNT_B"
|
||||
amount1="$AMOUNT_A"
|
||||
fi
|
||||
|
||||
case "$FEE" in
|
||||
100) tick_spacing=1 ;;
|
||||
500) tick_spacing=10 ;;
|
||||
3000) tick_spacing=60 ;;
|
||||
10000) tick_spacing=200 ;;
|
||||
*)
|
||||
echo "Unsupported fee tier for tick-spacing inference: $FEE" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
min_tick=-887272
|
||||
max_tick=887272
|
||||
tick_lower=$(( (min_tick / tick_spacing) * tick_spacing ))
|
||||
tick_upper=$(( (max_tick / tick_spacing) * tick_spacing ))
|
||||
|
||||
deadline="$(($(date +%s) + DEADLINE_SECONDS))"
|
||||
|
||||
echo "Funding Uniswap v3 pool"
|
||||
echo " token0: $token0 amount0: $amount0"
|
||||
echo " token1: $token1 amount1: $amount1"
|
||||
echo " fee: $FEE ticks: [$tick_lower, $tick_upper]"
|
||||
echo " recipient: $RECIPIENT"
|
||||
|
||||
if [[ "$EXECUTE" != "1" ]]; then
|
||||
echo "Dry run only."
|
||||
echo "Approve token0:"
|
||||
echo " cast send \"$token0\" 'approve(address,uint256)' \"$POSITION_MANAGER\" \"$amount0\" --rpc-url \"$RPC_URL\" --private-key \"\$PRIVATE_KEY\""
|
||||
echo "Approve token1:"
|
||||
echo " cast send \"$token1\" 'approve(address,uint256)' \"$POSITION_MANAGER\" \"$amount1\" --rpc-url \"$RPC_URL\" --private-key \"\$PRIVATE_KEY\""
|
||||
echo "Mint position:"
|
||||
echo " cast send \"$POSITION_MANAGER\" \"mint((address,address,uint24,int24,int24,uint256,uint256,uint256,uint256,address,uint256))\" \"($token0,$token1,$FEE,$tick_lower,$tick_upper,$amount0,$amount1,0,0,$RECIPIENT,$deadline)\" --rpc-url \"$RPC_URL\" --private-key \"\$PRIVATE_KEY\" -vv"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$PRIVATE_KEY" ]]; then
|
||||
echo "PRIVATE_KEY is required when EXECUTE=1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cast send "$token0" \
|
||||
"approve(address,uint256)" \
|
||||
"$POSITION_MANAGER" \
|
||||
"$amount0" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
-q
|
||||
|
||||
cast send "$token1" \
|
||||
"approve(address,uint256)" \
|
||||
"$POSITION_MANAGER" \
|
||||
"$amount1" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
-q
|
||||
|
||||
cast send "$POSITION_MANAGER" \
|
||||
"mint((address,address,uint24,int24,int24,uint256,uint256,uint256,uint256,address,uint256))" \
|
||||
"($token0,$token1,$FEE,$tick_lower,$tick_upper,$amount0,$amount1,0,0,$RECIPIENT,$deadline)" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
-vv
|
||||
66
scripts/deployment/verify-all-networks-explorers.sh
Executable file
66
scripts/deployment/verify-all-networks-explorers.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run all explorer verification entrypoints: Chain 138 Blockscout, Ethereum, multichain cW*,
|
||||
# Avax/Arb bridges, optional Cronos CCIP, optional CCIPLogger (Hardhat), optional Wemix.
|
||||
# Steps are best-effort (continue on failure); check log for FAILED lines.
|
||||
#
|
||||
# Usage: cd smom-dbis-138 && ./scripts/deployment/verify-all-networks-explorers.sh
|
||||
set -uo pipefail
|
||||
set +e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SMOM="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
# Parent of smom-dbis-138 = proxmox workspace root
|
||||
PROXMOX="$(cd "$SMOM/.." && pwd)"
|
||||
|
||||
STEPS_FAILED=0
|
||||
step() {
|
||||
local title="$1"
|
||||
shift
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "$title"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
if "$@"; then
|
||||
echo "[ok] $title"
|
||||
else
|
||||
echo "[fail] $title (exit $?)" >&2
|
||||
STEPS_FAILED=$((STEPS_FAILED + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# Load env for keys
|
||||
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "${SMOM}"
|
||||
fi
|
||||
|
||||
if [[ -f "$PROXMOX/scripts/verify/run-contract-verification-with-proxy.sh" ]]; then
|
||||
step "Chain 138 Blockscout (forge proxy)" bash "$PROXMOX/scripts/verify/run-contract-verification-with-proxy.sh" || true
|
||||
else
|
||||
echo "WARN: Proxmox verify proxy not found at $PROXMOX/scripts/verify/" >&2
|
||||
fi
|
||||
|
||||
step "Ethereum Mainnet CCIP bridges" bash "$SCRIPT_DIR/verify-mainnet-etherscan.sh" || true
|
||||
step "Ethereum Mainnet cW* (CompliantWrappedToken)" bash "$SCRIPT_DIR/verify-mainnet-cw-etherscan.sh" || true
|
||||
step "Multichain cW* (all CW*_* except MAINNET)" bash "$SCRIPT_DIR/verify-multichain-cw-etherscan.sh" || true
|
||||
step "Avalanche + Arbitrum WETH/CCIP bridges" bash "$SCRIPT_DIR/verify-deployed-contracts.sh" || true
|
||||
|
||||
# Bytecode + manual-verify instructions (no explorer API key required).
|
||||
step "Cronos deployments + manual verify runbook" bash "$SCRIPT_DIR/verify-cronos-contracts.sh" || true
|
||||
|
||||
if command -v npx &>/dev/null && [[ -f "$SMOM/hardhat.config.ts" || -f "$SMOM/hardhat.config.js" ]]; then
|
||||
step "CCIPLogger (Hardhat verify, multichain)" bash "$SCRIPT_DIR/verify-ccip-logger-other-chains.sh" || true
|
||||
else
|
||||
echo "Skip CCIPLogger: npx/hardhat config not available"
|
||||
fi
|
||||
|
||||
if [[ -n "${WEMIXSCAN_API_KEY:-}" && -n "${WEMIX_RPC:-}" && -n "${CCIPWETH9_BRIDGE_WEMIX:-}" ]]; then
|
||||
step "Wemix CCIP bridges" bash "$SCRIPT_DIR/verify-wemix-bridges.sh" || true
|
||||
else
|
||||
echo "Skip Wemix: WEMIXSCAN_API_KEY / WEMIX_RPC / CCIPWETH9_BRIDGE_WEMIX not all set"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== verify-all-networks-explorers finished: step failures=$STEPS_FAILED ==="
|
||||
exit 0
|
||||
105
scripts/deployment/verify-mainnet-cw-etherscan.sh
Executable file
105
scripts/deployment/verify-mainnet-cw-etherscan.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify Ethereum Mainnet CompliantWrappedToken (cWUSDT…cWXAUT) on Etherscan.
|
||||
# Constructor matches script/deploy/DeployCWTokens.s.sol (name, symbol, 6 decimals, deployer admin).
|
||||
#
|
||||
# Requires: ETHERSCAN_API_KEY, and either CW_VERIFY_ADMIN=0x... (deployer used at deploy) or PRIVATE_KEY
|
||||
# (address derived via cast wallet address) to ABI-encode constructor args.
|
||||
#
|
||||
# Usage:
|
||||
# cd smom-dbis-138 && ./scripts/deployment/verify-mainnet-cw-etherscan.sh
|
||||
# ./scripts/deployment/verify-mainnet-cw-etherscan.sh --dry-run # print forge commands only
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
DRY_RUN=false
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "${PROJECT_ROOT}"
|
||||
elif [[ -f "$PROJECT_ROOT/.env" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$PROJECT_ROOT/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
if [[ -z "${ETHERSCAN_API_KEY:-}" ]]; then
|
||||
echo "ETHERSCAN_API_KEY not set (see https://etherscan.io/myapikey)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ADMIN="${CW_VERIFY_ADMIN:-}"
|
||||
if [[ -z "$ADMIN" && -n "${PRIVATE_KEY:-}" ]]; then
|
||||
ADMIN="$(cast wallet address --private-key "$PRIVATE_KEY")"
|
||||
fi
|
||||
if [[ -z "$ADMIN" || "$ADMIN" == "0x0000000000000000000000000000000000000000" ]]; then
|
||||
echo "Set CW_VERIFY_ADMIN to the deployer address used in CompliantWrappedToken constructor, or set PRIVATE_KEY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_one() {
|
||||
local envkey="$1" cname="$2" sym="$3"
|
||||
local addr
|
||||
addr="${!envkey:-}"
|
||||
if [[ -z "$addr" ]]; then
|
||||
echo " skip $sym: $envkey unset"
|
||||
return 0
|
||||
fi
|
||||
local enc
|
||||
enc="$(cast abi-encode "constructor(string,string,uint8,address)" "$cname" "$sym" 6 "$ADMIN")"
|
||||
if $DRY_RUN; then
|
||||
echo "forge verify-contract --chain-id 1 --num-of-optimizations 200 --via-ir --constructor-args \"$enc\" --etherscan-api-key \"\$ETHERSCAN_API_KEY\" \"$addr\" contracts/tokens/CompliantWrappedToken.sol:CompliantWrappedToken"
|
||||
return 0
|
||||
fi
|
||||
set +e
|
||||
out="$(forge verify-contract \
|
||||
--chain-id 1 \
|
||||
--num-of-optimizations 200 \
|
||||
--via-ir \
|
||||
--constructor-args "$enc" \
|
||||
--etherscan-api-key "$ETHERSCAN_API_KEY" \
|
||||
"$addr" \
|
||||
"contracts/tokens/CompliantWrappedToken.sol:CompliantWrappedToken" 2>&1)"
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
echo " ok $sym $addr"
|
||||
elif echo "$out" | grep -qiE 'already verified|Already Verified'; then
|
||||
echo " already verified $sym $addr"
|
||||
else
|
||||
echo " FAILED $sym $addr (exit $rc)" >&2
|
||||
echo "$out" >&2
|
||||
return "$rc"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== Verify Mainnet CompliantWrappedToken (admin=$ADMIN) ==="
|
||||
# envVar|constructor name|symbol — must match DeployCWTokens.s.sol
|
||||
while IFS='|' read -r envkey cname sym; do
|
||||
[[ -z "$envkey" ]] && continue
|
||||
verify_one "$envkey" "$cname" "$sym"
|
||||
done <<'EOF'
|
||||
CWUSDT_MAINNET|Wrapped cUSDT|cWUSDT
|
||||
CWUSDC_MAINNET|Wrapped cUSDC|cWUSDC
|
||||
CWEURC_MAINNET|Wrapped cEURC|cWEURC
|
||||
CWEURT_MAINNET|Wrapped cEURT|cWEURT
|
||||
CWGBPC_MAINNET|Wrapped cGBPC|cWGBPC
|
||||
CWGBPT_MAINNET|Wrapped cGBPT|cWGBPT
|
||||
CWAUDC_MAINNET|Wrapped cAUDC|cWAUDC
|
||||
CWJPYC_MAINNET|Wrapped cJPYC|cWJPYC
|
||||
CWCHFC_MAINNET|Wrapped cCHFC|cWCHFC
|
||||
CWCADC_MAINNET|Wrapped cCADC|cWCADC
|
||||
CWXAUC_MAINNET|Wrapped cXAUC|cWXAUC
|
||||
CWXAUT_MAINNET|Wrapped cXAUT|cWXAUT
|
||||
EOF
|
||||
|
||||
echo "Done."
|
||||
130
scripts/deployment/verify-multichain-cw-etherscan.sh
Executable file
130
scripts/deployment/verify-multichain-cw-etherscan.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify CompliantWrappedToken (cW*) on all chains where addresses exist in .env.
|
||||
# Skips CW*_MAINNET (use verify-mainnet-cw-etherscan.sh). Same constructor as DeployCWTokens.
|
||||
#
|
||||
# Requires: ETHERSCAN_API_KEY (Etherscan v2 / unified explorers); CRONOSCAN_API_KEY for Cronos.
|
||||
# CW_VERIFY_ADMIN or PRIVATE_KEY for constructor admin address.
|
||||
#
|
||||
# Usage: cd smom-dbis-138 && ./scripts/deployment/verify-multichain-cw-etherscan.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "${PROJECT_ROOT}"
|
||||
elif [[ -f "$PROJECT_ROOT/.env" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$PROJECT_ROOT/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
if [[ -z "${ETHERSCAN_API_KEY:-}" ]]; then
|
||||
echo "ETHERSCAN_API_KEY not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ADMIN="${CW_VERIFY_ADMIN:-}"
|
||||
if [[ -z "$ADMIN" && -n "${PRIVATE_KEY:-}" ]]; then
|
||||
ADMIN="$(cast wallet address --private-key "$PRIVATE_KEY")"
|
||||
fi
|
||||
if [[ -z "$ADMIN" || "$ADMIN" == "0x0000000000000000000000000000000000000000" ]]; then
|
||||
echo "Set CW_VERIFY_ADMIN or PRIVATE_KEY" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Uppercase chain suffix -> forge --chain name (Foundry)
|
||||
forge_chain() {
|
||||
case "$1" in
|
||||
BSC) echo bsc ;;
|
||||
POLYGON) echo polygon ;;
|
||||
GNOSIS) echo gnosis ;;
|
||||
OPTIMISM) echo optimism ;;
|
||||
BASE) echo base ;;
|
||||
ARBITRUM) echo arbitrum ;;
|
||||
AVALANCHE) echo avalanche ;;
|
||||
CELO) echo celo ;;
|
||||
CRONOS) echo cronos ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# API key for forge (Cronos uses cronoscan)
|
||||
api_key_for() {
|
||||
case "$1" in
|
||||
CRONOS) echo "${CRONOSCAN_API_KEY:-}" ;;
|
||||
*) echo "${ETHERSCAN_API_KEY}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# cWUSDT -> Wrapped cUSDT / cWUSDT
|
||||
name_sym_from_cw() {
|
||||
local cw="$1"
|
||||
local core="${cw#CW}"
|
||||
echo "Wrapped c${core}|cW${core}"
|
||||
}
|
||||
|
||||
OK=0
|
||||
SKIP=0
|
||||
FAIL=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" =~ ^CW[A-Z0-9]+_[A-Z0-9]+= ]] || continue
|
||||
key="${line%%=*}"
|
||||
addr="${line#*=}"
|
||||
[[ -z "$addr" || "$addr" == "0x0000000000000000000000000000000000000000" ]] && continue
|
||||
chain="${key##*_}"
|
||||
[[ "$chain" == "MAINNET" ]] && continue
|
||||
# Cronos: Foundry + Etherscan API v2 often errors ("chainid"); use verify-cronos-contracts.sh or explorer.cronos.org manual upload.
|
||||
[[ "$chain" == "CRONOS" ]] && { echo " skip $key (Cronos: use verify-cronos-contracts.sh or CRONOS_VERIFICATION_RUNBOOK)"; SKIP=$((SKIP + 1)); continue; }
|
||||
|
||||
fc="$(forge_chain "$chain")"
|
||||
if [[ -z "$fc" ]]; then
|
||||
echo " skip $key (unknown chain suffix $chain)"
|
||||
SKIP=$((SKIP + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
api="$(api_key_for "$chain")"
|
||||
if [[ -z "$api" ]]; then
|
||||
echo " skip $key (no API key for $chain; set CRONOSCAN_API_KEY for Cronos)" >&2
|
||||
SKIP=$((SKIP + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
cwpart="${key%_*}"
|
||||
IFS='|' read -r cname sym <<< "$(name_sym_from_cw "$cwpart")"
|
||||
enc="$(cast abi-encode "constructor(string,string,uint8,address)" "$cname" "$sym" 6 "$ADMIN")"
|
||||
|
||||
echo "[$fc] $sym $addr ..."
|
||||
set +e
|
||||
out="$(forge verify-contract \
|
||||
--chain "$fc" \
|
||||
--num-of-optimizations 200 \
|
||||
--via-ir \
|
||||
--constructor-args "$enc" \
|
||||
--etherscan-api-key "$api" \
|
||||
"$addr" \
|
||||
"contracts/tokens/CompliantWrappedToken.sol:CompliantWrappedToken" 2>&1)"
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
echo " ok"
|
||||
OK=$((OK + 1))
|
||||
elif echo "$out" | grep -qiE 'already verified|Already Verified|Contract source code already verified'; then
|
||||
echo " already verified"
|
||||
OK=$((OK + 1))
|
||||
else
|
||||
echo " FAILED (exit $rc)" >&2
|
||||
echo "$out" >&2
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
sleep 1
|
||||
done < <(env | grep '^CW[A-Z0-9]*_' | sort)
|
||||
|
||||
echo ""
|
||||
echo "=== multichain cW*: ok/already=$OK skipped=$SKIP failed=$FAIL ==="
|
||||
14
scripts/hybx-omnl/ci-omnl-validation.sh
Executable file
14
scripts/hybx-omnl/ci-omnl-validation.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
bash scripts/hybx-omnl/verify-deployment.sh
|
||||
echo "== omnl reconcile artifact =="
|
||||
bash scripts/hybx-omnl/omnl-reconcile-artifact.sh
|
||||
cd services/token-aggregation
|
||||
if command -v pnpm >/dev/null 2>&1; then
|
||||
pnpm run build
|
||||
else
|
||||
npm run build
|
||||
fi
|
||||
echo "ci-omnl-validation: OK"
|
||||
40
scripts/hybx-omnl/omnl-reconcile-artifact.sh
Executable file
40
scripts/hybx-omnl/omnl-reconcile-artifact.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run OMNL IPSAS + journal-matrix anchor and persist JSON + sha256 under artifacts/omnl-reconcile/.
|
||||
# Usage: from repo root — bash scripts/hybx-omnl/omnl-reconcile-artifact.sh
|
||||
# Env:
|
||||
# OMNL_RECONCILE_ARTIFACT_DIR — output directory (default: $REPO_ROOT/artifacts/omnl-reconcile)
|
||||
# OMNL_IPSAS_GL_REGISTRY, OMNL_JOURNAL_MATRIX_PATH — passed through to omnl-reconcile-report.mjs
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
OUT="${OMNL_RECONCILE_ARTIFACT_DIR:-$ROOT/artifacts/omnl-reconcile}"
|
||||
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
|
||||
mkdir -p "$OUT"
|
||||
# Invoke the report script with node so stdout is JSON only (npm run adds lifecycle lines).
|
||||
node "$ROOT/services/token-aggregation/scripts/omnl-reconcile-report.mjs" > "$OUT/omnl-reconcile-${TS}.json"
|
||||
cp -f "$OUT/omnl-reconcile-${TS}.json" "$OUT/omnl-reconcile-latest.json"
|
||||
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const j = JSON.parse(fs.readFileSync('$OUT/omnl-reconcile-latest.json', 'utf8'));
|
||||
fs.writeFileSync('$OUT/omnl-reconcile-sha256.txt', String(j.sha256).trim() + '\n');
|
||||
"
|
||||
|
||||
if [[ -n "${GITHUB_SHA:-}" ]] || [[ -n "${GITHUB_RUN_ID:-}" ]]; then
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const o = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
githubSha: process.env.GITHUB_SHA || null,
|
||||
runId: process.env.GITHUB_RUN_ID || null,
|
||||
repository: process.env.GITHUB_REPOSITORY || null,
|
||||
};
|
||||
fs.writeFileSync('$OUT/omnl-reconcile-ci-meta.json', JSON.stringify(o, null, 2) + '\n');
|
||||
" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "OMNL reconcile artifacts written:"
|
||||
echo " $OUT/omnl-reconcile-${TS}.json"
|
||||
echo " $OUT/omnl-reconcile-latest.json"
|
||||
echo " $OUT/omnl-reconcile-sha256.txt ($(cat "$OUT/omnl-reconcile-sha256.txt" | tr -d '\n'))"
|
||||
66
scripts/hybx-omnl/sync-to-publish.sh
Executable file
66
scripts/hybx-omnl/sync-to-publish.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copy HYBX OMNL–related paths from smom-dbis-138 → smom-dbis-138-publish (adjust PUBLISH_ROOT if needed).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
PUBLISH_ROOT="${PUBLISH_ROOT:-$ROOT/../smom-dbis-138-publish}"
|
||||
if [[ ! -d "$PUBLISH_ROOT" ]]; then
|
||||
echo "error: PUBLISH_ROOT not found: $PUBLISH_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rsync -a "$ROOT/contracts/hybx-omnl/" "$PUBLISH_ROOT/contracts/hybx-omnl/"
|
||||
rsync -a "$ROOT/script/hybx-omnl/" "$PUBLISH_ROOT/script/hybx-omnl/"
|
||||
rsync -a "$ROOT/test/hybx-omnl/" "$PUBLISH_ROOT/test/hybx-omnl/"
|
||||
rsync -a "$ROOT/docs/hybx-omnl/" "$PUBLISH_ROOT/docs/hybx-omnl/"
|
||||
for f in hybx-omnl-policy.json hybx-omnl-cross-chain-lines.json omnl-ipsas-gl-registry.json omnl-journal-matrix.json deployment-omnl.example.env; do
|
||||
[[ -f "$ROOT/config/$f" ]] && cp -f "$ROOT/config/$f" "$PUBLISH_ROOT/config/$f"
|
||||
done
|
||||
rsync -a "$ROOT/scripts/hybx-omnl/" "$PUBLISH_ROOT/scripts/hybx-omnl/"
|
||||
rsync -a "$ROOT/scripts/forge/scope.sh" "$PUBLISH_ROOT/scripts/forge/scope.sh"
|
||||
mkdir -p "$PUBLISH_ROOT/.github/workflows"
|
||||
for w in hybx-omnl-ts.yml omnl-reconcile.yml; do
|
||||
[[ -f "$ROOT/.github/workflows/$w" ]] && cp -f "$ROOT/.github/workflows/$w" "$PUBLISH_ROOT/.github/workflows/$w"
|
||||
done
|
||||
|
||||
TA="$ROOT/services/token-aggregation"
|
||||
PTA="$PUBLISH_ROOT/services/token-aggregation"
|
||||
for f in omnl-webhooks.ts omnl-ipsas-gl.ts omnl-journal-matrix.ts omnl-compliance.ts omnl-policy-math.ts omnl-reconcile-anchor.ts omnl-integration-status.ts omnl-api-catalog.ts; do
|
||||
cp -f "$TA/src/services/$f" "$PTA/src/services/$f"
|
||||
done
|
||||
cp -f "$TA/src/services/omnl-reconcile-anchor.test.ts" "$PTA/src/services/omnl-reconcile-anchor.test.ts" 2>/dev/null || true
|
||||
cp -f "$TA/src/api/routes/omnl.ts" "$PTA/src/api/routes/omnl.ts"
|
||||
cp -f "$TA/src/api/routes/omnl-ipsas.ts" "$PTA/src/api/routes/omnl-ipsas.ts"
|
||||
cp -f "$TA/src/indexer/omnl-event-poller.ts" "$PTA/src/indexer/omnl-event-poller.ts"
|
||||
cp -f "$TA/src/indexer/omnl-poller-state.ts" "$PTA/src/indexer/omnl-poller-state.ts"
|
||||
cp -f "$TA/src/api/server.ts" "$PTA/src/api/server.ts"
|
||||
cp -f "$TA/src/api/middleware/rate-limit.ts" "$PTA/src/api/middleware/rate-limit.ts"
|
||||
mkdir -p "$PTA/src/api/middleware"
|
||||
cp -f "$TA/src/api/middleware/omnl-guards.ts" "$PTA/src/api/middleware/omnl-guards.ts"
|
||||
cp -f "$TA/src/api/middleware/omnl-guards.test.ts" "$PTA/src/api/middleware/omnl-guards.test.ts" 2>/dev/null || true
|
||||
cp -f "$TA/src/services/omnl-webhooks.test.ts" "$PTA/src/services/omnl-webhooks.test.ts" 2>/dev/null || true
|
||||
mkdir -p "$PTA/src/resources"
|
||||
cp -f "$TA/src/resources/omnl-openapi.json" "$PTA/src/resources/omnl-openapi.json"
|
||||
cp -f "$TA/public/omnl-dashboard.html" "$PTA/public/omnl-dashboard.html"
|
||||
cp -f "$TA/package.json" "$PTA/package.json"
|
||||
cp -f "$ROOT/docs/deployment/DEPLOYMENT_INDEX.md" "$PUBLISH_ROOT/docs/deployment/DEPLOYMENT_INDEX.md" 2>/dev/null || true
|
||||
cp -f "$ROOT/docs/MASTER_DOCUMENTATION_INDEX.md" "$PUBLISH_ROOT/docs/MASTER_DOCUMENTATION_INDEX.md" 2>/dev/null || true
|
||||
cp -f "$ROOT/README.md" "$PUBLISH_ROOT/README.md" 2>/dev/null || true
|
||||
|
||||
# Publish verify-deployment stays lightweight (no Forge)
|
||||
cat > "$PUBLISH_ROOT/scripts/hybx-omnl/verify-deployment.sh" <<'EOS'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
echo "== validate cross-chain config =="
|
||||
node scripts/hybx-omnl/validate-cross-chain-config.mjs
|
||||
cd services/token-aggregation
|
||||
echo "== token-aggregation tsc =="
|
||||
pnpm exec tsc --noEmit
|
||||
echo "== omnl reconcile report =="
|
||||
node scripts/omnl-reconcile-report.mjs >/dev/null
|
||||
echo "verify-deployment (publish): OK"
|
||||
EOS
|
||||
chmod +x "$PUBLISH_ROOT/scripts/hybx-omnl/verify-deployment.sh"
|
||||
|
||||
echo "sync-to-publish: OK → $PUBLISH_ROOT"
|
||||
53
scripts/hybx-omnl/validate-cross-chain-config.mjs
Executable file
53
scripts/hybx-omnl/validate-cross-chain-config.mjs
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate hybx-omnl-cross-chain-lines.json: structure, lineId hex, token addresses.
|
||||
* Exit 0 on success; exit 1 on validation errors (stderr).
|
||||
*/
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
function isAddr(s) {
|
||||
return typeof s === 'string' && /^0x[a-fA-F0-9]{40}$/.test(s);
|
||||
}
|
||||
|
||||
const root = resolve(__dirname, '../..');
|
||||
const configPath = process.env.OMNL_CROSS_CHAIN_CONFIG || resolve(root, 'config/hybx-omnl-cross-chain-lines.json');
|
||||
|
||||
function fail(msg) {
|
||||
console.error(msg);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const raw = readFileSync(configPath, 'utf8');
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
fail(`Invalid JSON: ${configPath}: ${e}`);
|
||||
}
|
||||
|
||||
if (!data.lines || !Array.isArray(data.lines)) {
|
||||
fail('Expected top-level { lines: [...] }');
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.lines.length; i++) {
|
||||
const row = data.lines[i];
|
||||
const lid = row.lineId;
|
||||
if (typeof lid !== 'string' || !/^0x[a-fA-F0-9]{64}$/.test(lid)) {
|
||||
fail(`lines[${i}].lineId must be bytes32 hex: ${lid}`);
|
||||
}
|
||||
if (!row.chains || typeof row.chains !== 'object') {
|
||||
fail(`lines[${i}].chains must be an object`);
|
||||
}
|
||||
for (const [cid, pair] of Object.entries(row.chains)) {
|
||||
if (!/^\d+$/.test(cid)) fail(`lines[${i}].chains key must be chain id string: ${cid}`);
|
||||
if (!pair || typeof pair !== 'object') fail(`lines[${i}].chains[${cid}] invalid`);
|
||||
const p = pair;
|
||||
if (!isAddr(p.tokenM0)) fail(`lines[${i}].chains[${cid}].tokenM0 invalid address: ${p.tokenM0}`);
|
||||
if (!isAddr(p.tokenM1)) fail(`lines[${i}].chains[${cid}].tokenM1 invalid address: ${p.tokenM1}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`OK: ${configPath} (${data.lines.length} line(s))`);
|
||||
12
scripts/hybx-omnl/verify-deployment.sh
Executable file
12
scripts/hybx-omnl/verify-deployment.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
echo "== validate cross-chain config =="
|
||||
node scripts/hybx-omnl/validate-cross-chain-config.mjs
|
||||
echo "== forge test hybx-omnl =="
|
||||
bash scripts/forge/scope.sh test hybx-omnl
|
||||
echo "== token-aggregation build =="
|
||||
cd services/token-aggregation
|
||||
pnpm exec tsc --noEmit
|
||||
echo "verify-deployment: OK"
|
||||
Reference in New Issue
Block a user