Exchange-rate voting, reward weighting, and oracle slashing.

Terra Classic validators feed external exchange rates so that downstream modules (tax splits, analytics, historical tooling) continue to operate even though algorithmic swaps are disabled. This module enforces the familiar prevote/vote handshake with slashing to keep price data honest.

Classic behaviour

  • Vote cadence. VotePeriodCopy VotePeriod is 30 seconds (5 blocks). Validators must broadcast aggregate prevotes in period t and reveal votes in period t+1. Same-period vote+prevote combinations are rejected (ErrRevealPeriodMissMatchCopy ErrRevealPeriodMissMatch).
  • Aggregate flow. Classic primarily uses MsgAggregateExchangeRatePrevoteCopy MsgAggregateExchangeRatePrevote and MsgAggregateExchangeRateVoteCopy MsgAggregateExchangeRateVote, bundling all whitelist denoms in a single message hashed as sha256("salt:rates:validator")Copy sha256("salt:rates:validator").
  • Whitelist management. Parameter WhitelistCopy Whitelist tracks the allowed fiat denoms (e.g., uusdCopy uusd, ukrwCopy ukrw). Governance can prune unused stablecoins; any denom missing sufficient voting power each period is temporarily removed from the on-chain store.
  • Reference Terra logic. Weighted medians are computed relative to the high-turnout denom (ReferenceTerraCopy ReferenceTerra) and converted into cross rates for the rest. Results land in the keeper via k.SetLunaExchangeRate()Copy k.SetLunaExchangeRate().
  • Reward funding. Oracle reward pools receive their budget from x/taxCopy x/tax splits. At each tally the module pays winners proportionally and decays the pool over RewardDistributionWindowCopy RewardDistributionWindow.
  • Miss counters & slashing. Validators must keep a valid vote rate above MinValidPerWindowCopy MinValidPerWindow (5%) over the week-long SlashWindowCopy SlashWindow. Offenders lose SlashFractionCopy SlashFraction (0.01%) of stake and are jailed until unjailed manually.

Messages

  • MsgAggregateExchangeRatePrevoteCopy MsgAggregateExchangeRatePrevote – commits the hash of all exchange rates for the next period (hash = sha256("salt:rate1,rate2,...:validator")Copy hash = sha256("salt:rate1,rate2,...:validator")). Multiple prevotes per period overwrite previous hashes.
  • MsgAggregateExchangeRateVoteCopy MsgAggregateExchangeRateVote – reveals the salt and comma-separated rate list. Each entry is formatted as <amount><denom>Copy <amount><denom> (e.g., 0.00012ukrwCopy 0.00012ukrw).
  • MsgDelegateFeedConsentCopy MsgDelegateFeedConsent – assigns a feeder account to a validator. CLI workflows (and the Classic E2E tests) confirm delegation before proceeding.

State

Store keyDescription
PrevotesCopy Prevotes (map[valoper][denom]AggregatePrevoteCopy map[valoper][denom]AggregatePrevote)Current-period hashes waiting for reveal.
VotesCopy Votes (map[valoper][denom]AggregateVoteCopy map[valoper][denom]AggregateVote)Revealed votes kept until tally completes.
LunaExchangeRateCopy LunaExchangeRate (map[denom]sdk.DecCopy map[denom]sdk.Dec)Latest accepted exchange rate per denom.
FeederDelegationsCopy FeederDelegations (map[valoper]accCopy map[valoper]acc)Delegated feeder accounts.
MissCountersCopy MissCounters (map[valoper]int64Copy map[valoper]int64)Missed-vote counters over the active slash window.

End-block workflow

  1. Check if the block height is a multiple of VotePeriodCopy VotePeriod.
  2. Build ballots per denom from revealed votes, discarding abstentions or jailed validators.
  3. Drop ballots lacking quorum (VoteThresholdCopy VoteThreshold, 50% voting power) or missing from WhitelistCopy Whitelist.
  4. Select ReferenceTerraCopy ReferenceTerra, compute cross rates, and derive weighted medians.
  5. Update on-chain exchange rates, reward ballot winners, and increment miss counters.
  6. When a SlashWindowCopy SlashWindow ends, slash and jail validators whose valid-vote ratio fell below MinValidPerWindowCopy MinValidPerWindow.
  7. Clear prevotes and votes for the next cycle while keeping the latest hash commitments.

Parameters (subspaceCopy subspace: oracleCopy oracle)

NameDescriptionClassic default
VotePeriodCopy VotePeriodBlocks per voting cycle.5Copy 5
VoteThresholdCopy VoteThresholdMinimum voting power required for a ballot.0.500000000000000000Copy 0.500000000000000000
RewardBandCopy RewardBandAllowed deviation around the weighted median.0.020000000000000000Copy 0.020000000000000000
RewardDistributionWindowCopy RewardDistributionWindowBlocks used to pro-rate the reward pool.10512000Copy 10512000 (BlocksPerYearCopy BlocksPerYear)
WhitelistCopy WhitelistAllowed denoms and their Tobin taxes.[ukrw, usdr, uusd, umnt]Copy [ukrw, usdr, uusd, umnt] (governance may update)
SlashFractionCopy SlashFractionStake slashed on oracle failure.0.000100000000000000Copy 0.000100000000000000
SlashWindowCopy SlashWindowBlock span for counting misses.604800Copy 604800 (BlocksPerWeekCopy BlocksPerWeek)
MinValidPerWindowCopy MinValidPerWindowRequired successful vote ratio.0.050000000000000000Copy 0.050000000000000000

Governance proposals targeting the oracleCopy oracle subspace can adjust any of these values.

CLI shortcuts

  • Submit aggregate vote: terrad tx oracle aggregate-vote <rates> <validator> --from <feeder>Copy terrad tx oracle aggregate-vote <rates> <validator> --from <feeder>
  • Delegate feeder: terrad tx oracle delegate-feed-consent <validator> <feeder>Copy terrad tx oracle delegate-feed-consent <validator> <feeder>
  • Inspect params: terrad q oracle paramsCopy terrad q oracle params
  • Check current prices: terrad q oracle exchange-ratesCopy terrad q oracle exchange-rates

Ensure feeders fund their accounts with enough ULUNA for fees. Classic tooling typically relies on endpoints such as https://terra-classic-lcd.publicnode.comCopy https://terra-classic-lcd.publicnode.com or https://api-lunc-lcd.binodes.comCopy https://api-lunc-lcd.binodes.com when scripting.