Know SVM Upgrade V0.0.4
Hi everyone,
This post serves as the comprehensive technical deep-dive into what the v0.0.4 upgrade actually does, why it is urgent, and exactly how validators must execute it.
If you are running an SVM validator, please read this document carefully. This is a state-breaking, coordinated upgrade. It is not safe to update ad-hoc or at your own pace.
What This Upgrade Is and Why It Is Urgent
Recently, Cosmos Labs disclosed a critical vulnerability: chains running cosmos/evm older than v0.6.2 or v0.7.2 must upgrade immediately.
The bug is a balance underflow in the EVM StateDB.SubBalance. If a transaction attempts to subtract more than an account's balance, it silently wraps around to ~2^256 instead of erroring. This has already been exploited to drain funds on MANTRA, TAC, and KiiChain (Aug 20–22, 2026) via a crafted vesting-account contract calling a balance-modifying precompile. Because the shared cmn.BalanceHandler code path is used by every precompile that touches account balances (staking, distribution, bank, ics20, gov, and SVM's own steembridge BridgeOut), this is exploitable by any user via a crafted transaction. No malicious validator is required.
SVM was pinned to the vulnerable cosmos/evm v0.7.1. This upgrade bundles three critical changes into one coordinated event to secure the network:
cosmos/evmv0.7.1 → v0.7.2: Applies the underflow fix (SubBalancenow panics on underflow instead of wrapping), aParseAmountfix, a feemarket gas-overflow clamp, and mirrors theProcessProposalhandler intoapp/evm.go.cosmos-sdkv0.54.3 → v0.54.4: A separate, upstream security release. Cosmos Labs recommends all chains upgrade via a coordinated event. We are bundling this here to avoid a second, separate upgrade event.- Withdrawal-Payout Hardening: Previously,
MsgAttestWithdrawalPayoutonly cross-checked(steem_txid, op_index)across validators. It never verified the actual amount or asset paid out on Steem. A validator manually relaying a payout who sent the wrong asset or amount could have that payout confirmed as valid anyway. Now, the keeper independently compares the validator-reportedamount_millisteemandassetagainst the withdrawal record. A mismatch is rejected (benign no-op, no confirmation recorded, audit event emitted) rather than silently trusted.
What Changed (For Code Reviewers)
For anyone reviewing the diff, here is a breakdown of the specific modifications:
go.mod/go.sum: The two version bumps mentioned above.app/upgrades.go: NewUpgradeNameV004 = "v0.0.4"handler, additive alongside the existing (dormant) v0.0.3 one. No store migrations are required, as neither dependency bump nor the payout change adds a new store key.app/evm.go:SetProcessProposalwired in alongside the existingSetPrepareProposal.app/ante_steembridge.go: Re-diffed against upstreamante/cosmos.goat v0.7.2. Confirmed no changes needed.Makefile:VERSIONchanged fromv0.0.3-Beta-2to0.0.4(dropped the leadingv). This fixes a latent, previously-unexercised bug:docker-entrypoint.shstages the cosmovisor upgrade binary atcosmovisor/upgrades/v$(steemvmd version)/bin/, prepending its ownv. AVERSIONvalue that already started withvproduced a double-vdirectory that could never match an on-chainPlan.Name. Verified via a real throwaway-container devnet boot thatsteemvmd versionnow prints0.0.4and stages correctly atcosmovisor/upgrades/v0.0.4/bin/steemvmd.precompiles/steembridge/steembridge.go: Doc comment added notingBridgeOut's shared exposure to the patched code path.proto/steemvm/steembridge/v1/tx.proto&x/oracle/bridge/types/tx.pb.go: Newamount_millisteem(field 7) andasset(field 8) fields onMsgAttestWithdrawalPayout.x/oracle/bridge/keeper/msg_server_attest_withdrawal_payout.go: The new amount/asset validation, mirrored into the fee-exemption ante-decorator acceptance check.- Event Updates: New
assetattribute on withdrawal/deposit created/minted/burned events, and a newwithdrawal_payout_asset_mismatchaudit event. - Oracle Clients (
oracle/go/,oracle/python/,oracle/js/): All three oracle client implementations updated in lockstep. The payout-detection logic now parses the actually observed amount/asset from the Steem transfer operation and reports it. It never echoes back the expected value, as that would defeat the on-chain check. (Seeoracle/PROTOCOL.md§5 for the spec).
Verification: go build, go vet, and go test ./... are all clean. A real devnet boot produced blocks with zero panics. All three oracle clients build, type-check, and pass their test suites.
Pending Verification
Not yet done, and blocking before this should be considered fully verified: A live exploit-replay test. We need to deploy a vesting-account contract via EVM, trigger the underflow through a precompile, and confirm it is cleanly rejected post-upgrade without crashing the node. This requires real EVM/Solidity tooling that is currently being set up. The upgrade is proceeding based on the upstream Cosmos Labs patch validation, but this local replay test will follow shortly.
Validator Upgrade Instructions
Path A: Docker Compose + Cosmovisor (Recommended Default)
Cosmovisor auto-swaps the binary at the exact upgrade height, but only if the new binary is already staged before that height arrives.
CRITICAL WARNING: Do NOT run docker compose down and docker compose up -d --build on your already-running validator before the upgrade height actually arrives. The docker-entrypoint.sh refreshes cosmovisor/genesis/bin/ on every container start whenever cosmovisor/current still points at genesis. Restarting your live container on the v0.0.4 image early would silently promote v0.0.4 to genesis/bin, and cosmovisor would start running it immediately, ahead of every other validator. This is an AppHash-divergence risk, not a safe pre-stage.
Instead, pre-stage the binary with a one-off service that never touches your running container:
Pull this branch to get the compose file's
stage-v0.0.4service (you do NOT need to rebuild or restart your live node for this step):git pull && git checkout release/v0.0.4-security-upgrade docker compose --profile stage run --rm stage-v0.0.4This pulls the published image, copies its binary straight into
cosmovisor/upgrades/v0.0.4/bin/steemvmd, and exits. It never touchescosmovisor/genesis/binand never runs cosmovisor, so your live node keeps running exactly what it is running now, completely undisturbed.Confirm the binary staged correctly in the
steemvm-nodecontainer:docker exec steemvm-node ls /root/.steemvm/cosmovisor/upgrades/v0.0.4/bin/MUST show:
steemvmddocker exec steemvm-node /root/.steemvm/cosmovisor/upgrades/v0.0.4/bin/steemvmd versionMUST print:
0.0.4Do this well before the target height. If the binary is not staged when the chain reaches the upgrade height, your node will halt until you stage it and restart.
At the upgrade height, cosmovisor halts the old process, swaps
currentto point atcosmovisor/upgrades/v0.0.4/, and restarts automatically. Watch your logs (docker compose logs -f steemvm-node) around the target height to confirm the swap and continued block production.Note on Backups: No automatic backup is taken (
docker-compose.ymlsetsUNSAFE_SKIP_BACKUP: "true"). If you want a rollback point, snapshot your node's data directory manually before the upgrade height.Only after the swap is confirmed is it safe to update your live
steemvmservice onto the published image for future restarts. At that point,cosmovisor/currentpoints atupgrades/v0.0.4, notgenesis, so the genesis-refresh no longer applies:docker compose pull steemvm-node && docker compose up -d steemvm-node
Path B: Bare steemvmd Binary (No Docker)
You do not need Docker to get cosmovisor's auto-swap. Installing cosmovisor as a sibling binary and running under it is the recommended path, as it avoids manual timing risks.
- Install cosmovisor once:
GOTOOLCHAIN=go1.25.10 go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@v1.7.0 - Set the environment variables cosmovisor needs (put these in your shell profile or systemd unit's
Environment=lines):export DAEMON_HOME=<your existing --home, e.g. $HOME/.steemvm> export DAEMON_NAME=steemvmd export DAEMON_RESTART_AFTER_UPGRADE=true export DAEMON_ALLOW_DOWNLOAD_BINARIES=false export UNSAFE_SKIP_BACKUP=true - Point cosmovisor's
genesis/binat your CURRENT (already-running, pre-upgrade) binary. If you initialize cosmovisor with the new v0.0.4 binary here, it becomes what cosmovisor runs on its very next start, ahead of the coordinated height.which steemvmd cosmovisor init /path/to/your/CURRENTLY-RUNNING/steemvmd - Build the new binary into a separate checkout or temp location so it does not overwrite the binary you just pointed
genesis/binat, then stage it into cosmovisor's upgrade slot directly:git pull && git checkout release/v0.0.4-security-upgrade make install NEWBIN="$(go env GOBIN)"; [ -z "$NEWBIN" ] && NEWBIN="$(go env GOPATH)/bin" "$NEWBIN/steemvmd" version # confirm: 0.0.4 mkdir -p "$DAEMON_HOME/cosmovisor/upgrades/v0.0.4/bin" cp "$NEWBIN/steemvmd" "$DAEMON_HOME/cosmovisor/upgrades/v0.0.4/bin/steemvmd" - Switch whatever supervises your node (systemd
ExecStart=, a screen/tmux command, etc.) fromsteemvmd start ...tocosmovisor run start ...(with the same flags), then restart once. This restart is a no-op in terms of behavior; cosmovisor'scurrentstill points atgenesis, so it launches the exact same binary you were already running, just supervised by cosmovisor now. - At the coordinated height, cosmovisor halts the old process, swaps to the staged v0.0.4 binary, and restarts automatically.
(Alternative: If you genuinely refuse to use cosmovisor, you must build the new binary ahead of time, watch the block height, and manually stop the old process and replace the binary at the exact halt height. This is highly discouraged due to timing risks.)
Governance Proposal: Action Required
This follows the standard cosmos-sdk software-upgrade governance flow. The proposal embeds an x/upgrade Plan with name = "v0.0.4" and a target height.
Proposal 2 is currently LIVE and open for voting. Because this is a critical security patch, timely voting by all operators is essential.
Vote on Proposal 2 Here: SoftwareUpgrade
How to Vote (Docker / Cosmovisor)
Run the following command to cast your Yes vote. Make sure to replace upex with your actual validator moniker/wallet name.
docker exec steemvm-node /root/go/bin/steemvmd tx gov vote 2 yes \
--from <your_moniker> \
--home /root/.steemvm \
--chain-id steemvm \
--gas auto \
--gas-adjustment 1.5 \
--gas-prices 1000000000asteem \
-y
(Example: If your moniker is blaze.apps, replace <your_moniker> with blaze.apps)
Sanity Checks After the Upgrade (All Operators)
Once the upgrade height has passed, run these checks to confirm success:
# 1. Ensure the node is synced and not catching up
docker exec steemvm-node /root/go/bin/steemvmd status | grep catching_up
# Should return: false
# 2. Verify the upgrade was applied at the correct height
docker exec steemvm-node /root/go/bin/steemvmd query upgrade applied v0.0.4 --home /root/.steemvm
# Should show the exact block height it applied at
Thank you to all validators for your swift attention to this critical security maintenance. Your diligence ensures the SVM ecosystem remains robust, secure, and ready for the future.
If you value proactive engineering, transparent development, and rigorous security standards for the STEEM ecosystem, please consider supporting my witness: blaze.apps

👍❤️