Files
proxmox/docs/03-deployment/CONTRACT_DEPLOYMENT_RUNBOOK.md
defiQUG e4c9dda0fd
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
chore: update submodule references and documentation
- Marked submodules ai-mcp-pmm-controller, explorer-monorepo, and smom-dbis-138 as dirty to reflect recent changes.
- Updated documentation to clarify operator script usage, including dotenv loading and task execution instructions.
- Enhanced the README and various index files to provide clearer navigation and task completion guidance.

Made-with: Cursor
2026-03-04 02:03:08 -08:00

21 KiB
Raw Blame History

Contract Deployment Runbook

Last Updated: 2026-02-12

Full deployment order: For the canonical sequence (prerequisites → core → PMM/pools → provider → optional → cW* → verification) and remaining recommendations, see DEPLOYMENT_ORDER_OF_OPERATIONS.md.

Deployment safety: Use RPC_URL_138 (Core only, from smom-dbis-138/.env) for all deployments; never use Public RPC. All secrets from smom-dbis-138/.env only. Run a gas/cost estimate before deploy (e.g. cd smom-dbis-138 && ./scripts/deployment/calculate-costs-consolidated.sh). Do not deploy when transactions are stuck — clear tx pool (./scripts/clear-all-transaction-pools.sh), wait ~60s, then retry; use scripts that check nonce when available.

Chain 138 deployment requirements (learned 2026-02-12)

  • Gas price: Chain 138 enforces a minimum gas price. Always use --with-gas-price 1000000000 (1 gwei) for forge script and forge create when deploying to Chain 138; otherwise transactions fail with "Gas price below configured minimum gas price".
  • Gas 32xxx when deploying: If you see gas-related RPC errors (e.g. -32000, execution reverted, or out of gas), add --gas-estimate-multiplier 150 (or 200) to forge script ... --broadcast so the broadcast uses a higher gas limit. See RPC_ERRORS_32001_32602.md.
  • On-chain check: After deployments, run ./scripts/verify/check-contracts-on-chain-138.sh (uses RPC_URL_138; optional URL arg). Address list comes from config/smart-contracts-master.json when available. See CONTRACT_ADDRESSES_REFERENCE, ADDRESS_MATRIX_AND_STATUS.
  • TransactionMirror: The deploy script can hit a Forge broadcast constructor-args decode error. If so, deploy manually: forge create contracts/mirror/TransactionMirror.sol:TransactionMirror --constructor-args <ADMIN_ADDRESS> --rpc-url $RPC_URL_138 --private-key $PRIVATE_KEY --gas-price 1000000000.

RPC Routing Summary

Chain 138 uses two standard env vars: RPC_URL_138 (Core, admin/deploy) and RPC_URL_138_PUBLIC (Public, bridge/frontend). See RPC_ENDPOINTS_MASTER.

Use Case VMID IP Ports Variable
Admin / contract deployment 2101 192.168.11.211 8545, 8546 RPC_URL_138 (Core)
Bridge, monitoring, public-facing 2201 192.168.11.221 (FIXED) 8545, 8546 RPC_URL_138_PUBLIC (Public)

Prerequisites

  1. .env check (keys only, no secrets printed): From repo root: ./scripts/deployment/preflight-chain138-deploy.sh (RPC, dotenv, nonce). Or from smom-dbis-138: ./scripts/deployment/check-env-required.sh — verifies PRIVATE_KEY, RPC_URL, RPC_URL_138 and optional PMM/mainnet/CCIP vars. Use smom-dbis-138/.env only for deploy secrets.

  2. Network access to Chain 138 RPC (set RPC_URL_138 in .env, e.g. http://192.168.11.211:8545 for Core)

    • Run from a host on the same LAN as Proxmox, or via VPN
    • WSL/remote dev environments may get "No route to host" if not on network
  3. PRIVATE_KEY in smom-dbis-138/.env (deployer wallet with gas; same wallet holds LINK for bridge fees)

  4. Foundry (forge) installed

  5. Test all contracts before deploy (Phase 0.8): Run ./scripts/deployment/test-all-contracts-before-deploy.sh from repo root. This runs forge build and forge test in smom-dbis-138. Use --dry-run to print commands only; --alltra to include alltra-lifi-settlement; --no-match "Fork|Mainnet|Integration|e2e" for unit tests only. See DEPLOYMENT_ORDER_OF_OPERATIONS § Phase 0.8.

Deprecated bridge (R4)

Do not use CCIPWETH9Bridge at 0x89dd.... Use only the canonical bridge at 0x971c... and set CCIPWETH9_BRIDGE_CHAIN138 in env. See docs/00-meta/RECOMMENDATIONS_OPERATOR_CHECKLIST.md R4.

Env required per deploy script

Script / phase Required env Notes
DeployMulticall, DeployOracle, DeployMultiSig PRIVATE_KEY, RPC_URL_138 (Chain 138 Core) deploy-all-contracts.sh
01_DeployCore, 02_DeployBridges PRIVATE_KEY, RPC; 02 needs UNIVERSAL_ASSET_REGISTRY, GOVERNANCE_CONTROLLER Phased core
DeployCCIPReceiver, DeployCCIPSender PRIVATE_KEY, CCIP_ROUTER_ADDRESS, ORACLE_AGGREGATOR_ADDRESS; Sender optional: LINK_TOKEN_ADDRESS Set in .env; see .env.example
DeployWETHBridges (mainnet receiver) MAINNET_WETH9_BRIDGE_ADDRESS, MAINNET_WETH10_BRIDGE_ADDRESS when configuring cross-chain .env.example
DeploySmartAccountsKit PRIVATE_KEY, RPC_URL_138; optional ENTRY_POINT, SMART_ACCOUNT_FACTORY, PAYMASTER if pre-deployed Script does not deploy contracts; obtain EntryPoint/Factory from MetaMask kit or ERC-4337 impl and set in env
DeployTransactionMirror PRIVATE_KEY, MIRROR_ADMIN (optional, default deployer) If forge script fails with constructor-args decode, use forge create — see § TransactionMirror below
DeployReserveSystem TOKEN_FACTORY in .env Phase 6

Deploy Core Contracts (Chain 138)

cd smom-dbis-138
source .env
# Verify RPC: curl -s -X POST "$RPC_URL" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

bash scripts/deployment/deploy-all-contracts.sh

Deploys: Multicall, Oracle, MultiSig (WETH9/10 pre-deployed in genesis).

Deploy Unified (Ordered or Parallel)

cd smom-dbis-138
./scripts/deployment/deploy-contracts-unified.sh --mode ordered
# or
./scripts/deployment/deploy-contracts-unified.sh --mode parallel

Deploy WETH Bridges (CCIP)

# From project root (use GAS_PRICE=1000000000 if min-gas-price error)
GAS_PRICE=1000000000 ./scripts/deploy-and-configure-weth9-bridge-chain138.sh
# Then set CCIPWETH9_BRIDGE_CHAIN138 in smom-dbis-138/.env

Smart accounts (ERC-4337)

Script: smom-dbis-138/script/smart-accounts/DeploySmartAccountsKit.s.sol (and DeployAccountWalletRegistryExtended.s.sol for registry).

Required env (in smom-dbis-138/.env): PRIVATE_KEY, RPC_URL_138 (Chain 138 Core). Optional: set ENTRY_POINT, SMART_ACCOUNT_FACTORY, PAYMASTER if already deployed; otherwise the script will deploy and log addresses to set in .env.

Deploy (Chain 138):

cd smom-dbis-138
source .env
forge script script/smart-accounts/DeploySmartAccountsKit.s.sol --rpc-url $RPC_URL_138 --broadcast --with-gas-price 1000000000
# Set ENTRY_POINT, SMART_ACCOUNT_FACTORY, PAYMASTER from output

Verification: Run script; confirm logged addresses match env. See PLACEHOLDERS_AND_TBD Smart Accounts Kit.

TransactionMirror (Chain 138)

Script: script/DeployTransactionMirror.s.sol. Deployed address: Set in smom-dbis-138/.env as TRANSACTION_MIRROR_ADDRESS from the script output (e.g. past deploys: 0xE362aa10D3Af1A16880A799b78D18F923403B55a, 0x4eeF36BBaf706C6da5859cF9B34E9934fEC3E006).

Recommended: Use the combined script; it always checks nonce, validates RPC is active (chainId 138), uses proper gas (1 gwei min), and loads the correct dotenv (smom-dbis-138/.env + config/ip-addresses.conf for RPC fallbacks).

Required in smom-dbis-138/.env: PRIVATE_KEY, RPC_URL_138 (Core RPC, 192.168.11.211:8545). No Public fallback for deployments. Optional: GAS_PRICE or GAS_PRICE_138 (default 1000000000). Before deploying: if Core was read-only, run ./scripts/maintenance/make-rpc-vmids-writable-via-ssh.sh then ./scripts/maintenance/health-check-rpc-2101.sh. See RPC_2101_READONLY_FIX.md.

If you see "Known transaction" or "Replacement transaction underpriced": Clear the tx pool then retry: ./scripts/clear-all-transaction-pools.sh (or RPC-only; see script). Run from a host that can reach RPC_URL_138 (same LAN/VPN):

./scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh

This deploys TransactionMirror and creates the DODO cUSDT/cUSDC PMM pool, then runs on-chain verification. Core RPC only (no Public fallback). If Core is unreachable, fix read-only and health first (see RPC_2101_READONLY_FIX.md). Options: --dry-run (env, RPC, nonce only); --force (skip RPC check).

Skip stuck nonce manually: Set NEXT_NONCE to the next nonce (e.g. 13370) so the script uses vm.setNonce and deploys at a new address; then set TRANSACTION_MIRROR_ADDRESS in .env to the logged address. The combined script already sets NEXT_NONCE from pending nonce.

Or run the two forge commands manually (ensure RPC is Chain 138 and nonce is correct):

cd smom-dbis-138 && source .env
# Optional: export NEXT_NONCE=<pending nonce> if avoiding a stuck tx
forge script script/DeployTransactionMirror.s.sol:DeployTransactionMirror --rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000
forge script script/dex/CreateCUSDTCUSDCPool.s.sol:CreateCUSDTCUSDCPool --rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000

If forge script fails with "Failed to decode constructor arguments", deploy via forge create:

cd smom-dbis-138
source .env
ADMIN="${MIRROR_ADMIN:-$(cast wallet address --private-key $PRIVATE_KEY)}"
forge create contracts/mirror/TransactionMirror.sol:TransactionMirror \
  --constructor-args "$ADMIN" \
  --rpc-url "$RPC_URL_138" \
  --private-key "$PRIVATE_KEY" \
  --legacy \
  --gas-price 1000000000

Or run the helper script (from repo root, from a host on LAN that can reach RPC_URL_138 e.g. 192.168.11.211:8545): ./scripts/deployment/deploy-transaction-mirror-chain138.sh. The script exports ETH_RPC_URL so Forge uses the correct RPC and, on success, updates or appends TRANSACTION_MIRROR_ADDRESS in smom-dbis-138/.env.

AlltraAdapter — setBridgeFee after deploy

After deploying or using AlltraAdapter (138 ↔ ALL Mainnet 651940), set the bridge fee to match ALL Mainnet fee structure.

Required: ALLTRA_ADAPTER_CHAIN138 in smom-dbis-138/.env (adapter address; see config/smart-contracts-master.json). Optional: ALLTRA_BRIDGE_FEE (wei) to pass to setBridgeFee. Deployer must have DEFAULT_ADMIN_ROLE.

cd smom-dbis-138 && source .env
# Use ALLTRA_BRIDGE_FEE from .env if set, else 0.001 ALL (1000000000000000 wei)
FEE="${ALLTRA_BRIDGE_FEE:-1000000000000000}"
cast send "$ALLTRA_ADAPTER_CHAIN138" "setBridgeFee(uint256)" "$FEE" \
  --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000

Document the final fee in PLACEHOLDERS_AND_TBD § AlltraAdapter when known.

Vault ac* / vdc* / sdc* (Chain 138)

After deploying the Vault System (DeployVaultSystem.s.sol), run the single script that creates all asset/deposit (ac*) and debt (vdc*) tokens via VaultFactory.createVaultWithDecimals.

Required env: VAULT_FACTORY_ADDRESS, PRIVATE_KEY, RPC_URL_138. Optional: OWNER, ENTITY (default deployer); CUSDC_ADDRESS_138, CUSDT_ADDRESS_138 or COMPLIANT_USDC_ADDRESS, COMPLIANT_USDT_ADDRESS (skip currency if unset).

Option A — deploy Vault System then ac/vdc/sdc in one go: run ./scripts/deployment/deploy-vault-system-and-ac-vdc-sdc.sh from smom-dbis-138 (uses PRIVATE_KEY, RPC_URL_138; parses VaultFactory from broadcast and runs ac/vdc/sdc step).

Option B — run steps separately:

cd smom-dbis-138
source .env
forge script script/deploy/vault/DeployVaultSystem.s.sol:DeployVaultSystem \
  --rpc-url "$RPC_URL_138" --broadcast --with-gas-price 1000000000
# From output, set VAULT_FACTORY_ADDRESS=0x... then:
forge script script/deploy/vault/DeployAcVdcSdcVaults.s.sol:DeployAcVdcSdcVaults \
  --rpc-url "$RPC_URL_138" --broadcast --with-gas-price 1000000000

Deployer must have VAULT_DEPLOYER_ROLE on VaultFactory. Each configured base token gets one vault with deposit token (ac*) and debt token (vdc*), 6 decimals, transferable debt. See MULTI_CHAIN_EXECUTION_DETERMINISTIC_DEPLOYMENT. Full architecture and phased roadmap: see VAULT_SYSTEM_MASTER_TECHNICAL_PLAN.

EnhancedSwapRouter & DODOPMMProvider (post-deploy configuration)

When Uniswap V3, Balancer, or DODO PMM pools exist on Chain 138 / 651940, configure the router and provider so on-chain quotes and swaps work.

EnhancedSwapRouter (set by address with ROUTING_MANAGER_ROLE):

Config Method Env (optional) When
Uniswap V3 Quoter setUniswapQuoter(address) ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER After Uniswap Quoter is deployed on chain
Balancer pool (WETH↔stable) setBalancerPoolId(tokenIn, tokenOut, poolId) BALANCER_WETH_USDC_POOL_ID, BALANCER_WETH_USDT_POOL_ID After Balancer pool exists
Dodoex pool setDodoPoolAddress(tokenIn, tokenOut, pool) Set via cast or script when DODO pool is known After DODO PMM pool deployed

Example (Chain 138, after setting env):

cd smom-dbis-138 && source .env
# Uniswap Quoter
[ -n "$ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER" ] && cast send "$ENHANCED_SWAP_ROUTER" "setUniswapQuoter(address)" "$ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER" --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000
# Balancer (bytes32 pool IDs; use cast send with 0x-prefixed hex)
# cast send "$ENHANCED_SWAP_ROUTER" "setBalancerPoolId(address,address,bytes32)" <WETH> <USDC> "$BALANCER_WETH_USDC_POOL_ID" ...

DODOPMMProvider: Register existing DODO PMM pools so getQuote / executeSwap work. Address with POOL_MANAGER_ROLE calls registerPool(tokenIn, tokenOut, pool).

# After DODO pool is deployed (e.g. cUSDT↔USDT)
cast send "$DODO_PMM_PROVIDER_ADDRESS" "registerPool(address,address,address)" "<CUSDT>" "<USDT>" "<POOL_ADDRESS>" --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000

Optional .env placeholders (see smom-dbis-138/.env.example): ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER, BALANCER_WETH_USDC_POOL_ID, BALANCER_WETH_USDT_POOL_ID, DODO_PMM_PROVIDER_ADDRESS. Until set, router returns 0 for Uniswap/Balancer quotes and DODO provider returns no pool.

Private stabilization pools (Master Plan Phase 2)

XAU-anchored private pools (cUSDT↔XAU, cUSDC↔XAU, cEURT↔XAU) for the Stabilizer; only the Stabilizer or whitelisted keeper should execute swaps. See VAULT_SYSTEM_MASTER_TECHNICAL_PLAN §5.

Script: smom-dbis-138/script/dex/DeployPrivatePoolRegistryAndPools.s.sol — deploys PrivatePoolRegistry and optionally creates pools via DODOPMMIntegration createPool, then registers them in the registry.

Env (in smom-dbis-138/.env):

Variable Description
PRIVATE_KEY Deployer (must have POOL_MANAGER_ROLE on DODOPMMIntegration to create pools)
PRIVATE_POOL_REGISTRY_ADMIN Admin for PrivatePoolRegistry (default: deployer)
DODOPMM_INTEGRATION_ADDRESS Deployed DODOPMMIntegration (set to create XAU pools in same run)
XAU_ADDRESS_138 XAU token address on Chain 138 (required to create XAU-anchored pools)
COMPLIANT_USDT_ADDRESS cUSDT on Chain 138
COMPLIANT_USDC_ADDRESS cUSDC on Chain 138
cEURT_ADDRESS_138 cEURT on Chain 138 (optional; omit to skip cEURT↔XAU pool)

Deploy:

cd smom-dbis-138 && source .env
forge script script/dex/DeployPrivatePoolRegistryAndPools.s.sol:DeployPrivatePoolRegistryAndPools \
  --rpc-url "$RPC_URL_138" --broadcast --with-gas-price 1000000000

If only the registry is needed (pools created later), leave DODOPMM_INTEGRATION_ADDRESS or XAU_ADDRESS_138 unset. To register existing pools manually: cast send "$PRIVATE_POOL_REGISTRY" "register(address,address,address)" <TOKEN_A> <TOKEN_B> <POOL_ADDRESS> .... Grant STABILIZER_LP_ROLE to allowed LPs when using a wrapper that checks it.

Stabilizer deployment and configuration (Phase 3 + 6)

Deploy the Stabilizer (Master Plan Phase 3) after PrivatePoolRegistry and private XAU-anchored pools exist. The Stabilizer calls checkDeviation() (peg manager or TWAP) and executePrivateSwap(tradeSize, tokenIn, tokenOut) via the private pool registry. Phase 6: TWAP/sustained N-block deviation, per-block volume cap, flash drain recovery target <3 blocks (see OPERATIONS_RUNBOOK Flash Loan Containment).

Deploy (e.g. via forge create or a small script):

  • Constructor: (admin, privatePoolRegistryAddress).
  • Set in .env: STABILIZER_ADDRESS, PRIVATE_POOL_REGISTRY_ADDRESS, STABLECOIN_PEG_MANAGER_ADDRESS (or commodity peg), peg asset address.

Configuration (admin):

Parameter Method Typical / notes
Peg source setStablecoinPegSource(manager, asset) or setCommodityPegSource(manager, asset) One of them; deviation from peg used for checkDeviation()
thresholdBps setThresholdBps(uint256) e.g. 50 (0.5%)
minBlocksBetweenExecution setMinBlocksBetweenExecution(uint256) e.g. 35 (block delay)
maxStabilizationVolumePerBlock setMaxStabilizationVolumePerBlock(uint256) Cap per block
maxSlippageBps setMaxSlippageBps(uint256) e.g. 100 (1%)
maxGasPriceForStabilizer setMaxGasPriceForStabilizer(uint256) MEV resistance; 0 = disabled
sustainedDeviationBlocks setSustainedDeviationBlocks(uint256) N blocks over threshold before rebalance (Phase 6)

Keeper: Grant STABILIZER_KEEPER_ROLE to the keeper EOA or bot:
cast send "$STABILIZER_ADDRESS" "grantRole(bytes32,address)" $(cast keccak "STABILIZER_KEEPER_ROLE()") "$KEEPER_ADDRESS" --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000

Operational target (Phase 6): Flash drain recovery <3 blocks. The contract enforces sustained deviation over N blocks, per-block volume cap, and block delay; document in OPERATIONS_RUNBOOK and VAULT_SYSTEM_MASTER_TECHNICAL_PLAN §8/§16.

Contract Verification (Blockscout)

Use the Forge Verification Proxy for forge verify-contract (Blockscout expects module/action in query; Forge sends JSON only). The verification script uses canonical addresses from smom-dbis-138/.env and config/ip-addresses.conf (ORACLE_PROXY, AGGREGATOR_ADDRESS, CCIP_SENDER, CCIPWETH9_BRIDGE_CHAIN138, etc.); run from a host on LAN that can reach Blockscout (192.168.11.140:4000).

Preferred: orchestrated script (starts proxy if needed, timeout 900s default):

source smom-dbis-138/.env 2>/dev/null
./scripts/verify/run-contract-verification-with-proxy.sh

Manual (proxy + verify):

# 1. Start proxy (in separate terminal)
BLOCKSCOUT_URL=http://192.168.11.140:4000 node forge-verification-proxy/server.js

# 2. Run verification
./scripts/verify-contracts-blockscout.sh

See: forge-verification-proxy/README.md, BLOCKSCOUT_FORGE_VERIFICATION_EVALUATION.md. Fallback: manual verification at https://explorer.d-bis.org/address/#verify-contract

Runbooks in sync (R12): BLOCKSCOUT_FIX_RUNBOOK, BLOCKSCOUT_FORGE_VERIFICATION_EVALUATION, this runbook. Full recommendations (R1R24): RECOMMENDATIONS_OPERATOR_CHECKLIST.


Troubleshooting

Error Cause Fix
No route to host Dev machine cannot reach RPC (RPC_URL_138, e.g. 192.168.11.211:8545) Run from machine on LAN or VPN; or set RPC_URL_138=https://rpc-core.d-bis.org
PRIVATE_KEY not set Missing in .env Add deployer key to smom-dbis-138/.env
Gas price below configured minimum gas price Chain 138 minimum gas not met Use --with-gas-price 1000000000 for all forge script / forge create on Chain 138
RPC -32xxx / out of gas when deploying Gas estimate too low or estimation failed Use --gas-estimate-multiplier 150 (or 200) with forge script ... --broadcast; ensure deployer has enough ETH. See RPC_ERRORS_32001_32602.md.
Failed to decode constructor arguments (TransactionMirror) Forge broadcast decode bug Deploy via forge create ... --constructor-args <ADMIN> --gas-price 1000000000
pam_chauthtok failed (Blockscout) Container PAM restriction Use Proxmox Web UI: Container 5000 → Options → Password
pvesm not found (verify-storage) Script must run ON Proxmox host ssh root@r630-01 then run script