Terra Classic LUNC Go core fixes are in classic-terra/core across two releases. v4.0.2, published 28 September 2026, has the staking debug prints, the oracle EndBlocker logs, and the dyncomm genesis import. v4.1.0, published 4 October 2026, has the two tax-module fixes. Luna Classic is that chain. The chain id in the staking hook is columbus-5.
I opened the pull requests as GeoffreySHD. The v4.0.2 notes tell operators to stop terrad, replace the binary, and start it again at any height. The same notes say the release runs beside nodes still on v4.0.1-patch.3, and that columbus-5 needs no state migration for v4.0.2.
Staking debug prints on delegation
Pull request 672 deletes the fmt.Printf calls in TerraStakingHooks.AfterDelegationModified (custom/staking/hook.go). ColumbusChainID is the string columbus-5. The first print ran before that check, so every delegation logged DEBUG: Hook called. Any other chain ID then logged DEBUG: Chain ID mismatch and returned. On columbus-5, block height 0 logged DEBUG: Genesis block, skipping and returned.
Past height 0, once the validator was loaded and bonded power was non-zero, three more lines followed: validator power against the bonded total, the delegation percent against the 20% threshold (math.LegacyNewDecWithPrec(20, 2)), and either DEBUG: Hook passed validation or DEBUG: Returning error - validator power over limit. The 20% check is still in the function, and the hook still walks every validator to sum bonded power. bondedCount existed only to fill one print, and it came out with the prints. GitHub records 22 deletions and 0 additions in hook.go. The same pull request points the Osmosis image in tests/interchaintest/ibc_pfm_test.go at ghcr.io/amygdala-labs/heighliner/osmosis, after the strangelove-ventures GHCR namespace stopped resolving.
Before
After
Those lines never reached the tax module. They hit stdout. A node operator grepping a crash from that afternoon can drown in DEBUG: Hook called lines from every delegation that day before they find the real stack. Or they ship journald into a vendor that bills on ingestion and pay for junk lines that never helped find the crash.
The burn tax is a different bill, on a different set of messages. FilterMsgAndComputeTax in custom/auth/ante/fee_tax.go runs ComputeTaxes for bank sends, bank multi-sends, market swaps, and CosmWasm instantiate or execute funds, with the exemption checks in that switch. Staking delegation messages are absent from the switch. ComputeTaxes multiplies the coin amount by burn_tax_rate, skips the SDK bond denom (the unit test treats that denom as stake) and IBC denoms, then clamps the result to the treasury tax cap.
Live columbus-5 params at https://terra-classic-lcd.publicnode.com/terra/tax/v1beta1/params report burn_tax_rate of 0.015. The treasury cap for uluna is 60000000000000000 base units (https://terra-classic-lcd.publicnode.com/terra/treasury/v1beta1/tax_caps/uluna). The chain registry lists luna at exponent 6, so that cap is 60,000,000,000 LUNC. A debug print does not change the rate or the cap.
Oracle EndBlocker returned before the tally
Pull request 668 is also in v4.0.2. On the last block of an oracle vote period, EndBlocker in x/oracle/abci.go calls StakingKeeper.MaxValidators and StakingKeeper.ValidatorsPowerStoreIterator. If either call returned an error, the function returned on the spot.
That return sits above the rest of the vote-period work in the same function: the claim map, the wipe of Luna exchange rates, the ballot tally, SetLunaExchangeRateWithEvent, the miss counters, and RewardBallotWinners. The period was skipped. The log had no line explaining why. Prices go stale for a vote period, the operator opens the log and finds nothing that says the EndBlocker bailed, so they restart nodes or blame RPC before they ever see a keeper error.
The merged patch writes the error, then returns. The vote period is still skipped. The two messages are failed to get max validators; skipping oracle vote period and failed to iterate validator power store; skipping oracle vote period. An operator whose prices have stopped can search for skipping oracle vote period and read the keeper error on that line.
Before
After
Dyncomm InitGenesis ignored the exported rates
Pull request 671 is the third v4.0.2 change. ExportGenesis writes ValidatorCommissionRates. InitGenesis never read that slice. It walked every validator and set the target commission to val.Commission.Rate, the rate on the staking validator record. A chain started from an export came up with those staking rates in the dyncomm target store.
The fix writes exported min and target rates first. The staking-rate default runs only when the min-rate key for that validator is empty. keeper.StoreKey() was added so InitGenesis can do that read.
Before
After
TestGenesisRoundTrip stores a min of 0.05 and a target of 0.03, exports, imports into a fresh keeper, and requires both values back. Those two decimals are the fixture in x/dyncomm/genesis_test.go. They show the round trip keeping the stored rates. v4.0.2 does not migrate a chain that is already running. This path runs the next time someone calls InitGenesis on an export.
Without the restore, after a genesis import from export a validator looks at dyncomm targets that no longer match what they set, and someone has to explain a commission rate change nobody voted for.
IsReverseCharge panicked off the ante path
Pull request 667 merged to main on 3 October 2026. It is in the v4.1.0 release. Keeper.IsReverseCharge in x/tax/keeper/keeper.go did an unchecked type assert:
if !ctx.Value(types.ContextKeyTaxReverseCharge).(bool) {
The fee ante handler is what stores that flag. A query, a genesis call, or any other context that never passed through the ante decorator got nil from ctx.Value, and the assert panicked the process.
The replacement uses the comma-ok form. A missing flag or a wrong type is treated as false:
reverseCharge, ok := ctx.Value(types.ContextKeyTaxReverseCharge).(bool)
if !ok || !reverseCharge {
TestIsReverseCharge covers an absent flag, explicit true, explicit false, a wrong type, and whether the no-reverse-charge event is emitted. An absent flag used to panic. It now returns false. A query or tooling path that never ran the fee ante handler used to take the process down with a panic instead of answering.
Community-tax split divided by zero
Pull request 670 is in the same v4.1.0 release. ProcessTaxSplits computed the community-tax adjustment inline:
communityTax.Mul(oracleSplitRate.Quo(
communityTax.Mul(oracleSplitRate).Add(sdkmath.LegacyOneDec()).Sub(communityTax),
))
The divisor is communityTax * (1 - oracleSplitRate). It is zero when communityTax is 1 and oracleSplitRate is 0. The comment on CommunityTaxAdjustment calls both of those governance-settable parameters. LegacyDec division then panics inside ProcessTaxSplits, on transactions that have already produced tax coins (the bank, market, and CosmWasm cases above).
types.CommunityTaxAdjustment returns the original communityTax when that denominator is not positive. For every other pair it keeps the old order, communityTax * (oracleSplitRate / denominator). LegacyDec rounds on each operation, so regrouping the formula would change the last decimals of a split that already worked.
TestCommunityTaxAdjustment checks a known pair: community tax 0.02 and oracle split 0.5, expected result 0.01 / 0.99. The case that used to panic is community tax 1 with oracle split 0. That call now returns 1. A governance pair that is individually legal (community tax 100%, oracle split 0) used to panic mid-tax on taxable txs and stop those paths until someone reverted the params.
If you have a module open
CosmWasm dApps on a Cosmos SDK chain hit the same keeper calls and the same ante path. Terra Classic node and contract work is the Luna Classic service.
If you have a module, a CosmWasm contract, or a genesis export that loses commission rates on import, talk to Sky High Designs and send the repository.

