feat(api): Asset/Price primitives with zero-float steemd semantics#13
Merged
Conversation
Add the lowest-level Asset/Price primitives to protocol/api, filling the gap where wire types only "carry but don't interpret" string-asset fields (e.g. "1.500 STEEM"). Every consumer previously had to split+parse; now a shared, exact primitive backs steemutil wire types, steemgosdk, and the upcoming conveyor Go rewrite. Design: align with steemd chain semantics, zero float end-to-end. - Asset.Amount is int64 (on-chain atomic units); precision is derived from symbol (STEEM/SBD=3, VESTS=6), capped at STEEM_MAX_SATOSHIS (2^62-1). - Price math uses math/big.Int (stdlib, covers 128-bit), replicating steemd "multiply-then-divide + overflow check"; Compare cross-multiplies. - dsteem's float64 is a JS legacy and explicitly not followed. A1. asset.go : Asset + ParseAsset/String/Add/Sub, int-only parse path. A2. price.go : Price + ParsePrice/Convert/Compare/Invert, big.Int math. A3. market.go : OrderPrice/CurrentMedianHistoryPrice ToPrice() bridges. A4. prices.go : ComputePrices over wire types (conveyor get_prices, int avg). Tests: deterministic unit tests + go:embed testdata fixtures covering round-trip, malformed input, 0.1+0.2 zero-roundoff, Convert golden vectors (incl. 128-bit intermediate), overflow guards, cross-mult Compare, Invert round-trip, and ComputePrices exact atoms. go1.18 compatible, no new third-party deps.
Address four defects found in the self-audit of PR #13, each with a deterministic regression test: 1. ComputePrices.resultSymbol was hardcoded "SBD". When an order book contained a non-STEEM/SBD pair (e.g. STEEM/VESTS), Convert returned VESTS atoms but the result was mislabeled "SBD" — silent corruption whose String() output was wrong by both symbol and 1000x value (VESTS 6dp vs SBD 3dp). Now: infer symbol from the first Convert, require all orders to yield a consistent SBD result, and error on any non-STEEM/SBD order. 2. Asset arithmetic was asymmetric with ParseAsset. Sub documented "results may be negative" yet ParseAsset rejects negatives, breaking the String<->ParseAsset round-trip. Add/Sub also only checked overflow when b.Amount > 0, missing negative-operand cases. Now: Sub rejects negative results (matches ParseAsset's non-negative invariant), Add uses bits.Add64 for sign-independent carry check. 3. Asset.Precision() silently returned 0 for unknown symbols, so String() emitted e.g. "100 UNKNOWN" with no error. Now: panic on unknown symbol — an Asset with an illegal symbol is a programmer error (only reachable via Asset{} literal bypassing ParseAsset). 4. Price.Convert documented its precondition: Price must be constructed via ParsePrice (guarantees non-zero Base/Quote, valid symbols). Direct Price{} literals bypass validation and are undefined behavior. All tests pass: go test ./... green, go vet clean, gofmt clean.
kuny0707
approved these changes
Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the lowest-level
Asset/Priceprimitives toprotocol/api, filling the gap where wire types only "carry but don't interpret" string-asset fields (e.g."1.500 STEEM"). Every consumer previously had tosplit(' ')+ parse on its own; now a shared, exact primitive backs steemutil wire types, steemgosdk, and the upcoming conveyor Go rewrite.Implements the plan in
asset-price-plan.md(A1–A4), targeting v0.0.25.Design decision: align with steemd chain semantics, zero float end-to-end
Asset.Amountisint64(on-chain atomic units); precision is derived from symbol (STEEM/SBD=3, VESTS=6), capped atSTEEM_MAX_SATOSHIS(2^62-1).math/big.Int(stdlib, covers 128-bit), replicating steemd "multiply-then-divide + overflow check";Comparecross-multiplies.float64is a JS legacy and is explicitly not followed — conveyor's price index is a fresh Go design; the SDK layer only provides exact integer primitives, and the display layer converts to float as needed.Changes
protocol/api/asset.go(new)Asset+ParseAsset/String/Add/Sub, int-only parse path (no float transit)protocol/api/price.go(new)Price+ParsePrice/Convert/Compare/Invert,big.Intmathprotocol/api/market.goOrderPrice.ToPrice()/CurrentMedianHistoryPrice.ToPrice()bridgesprotocol/api/prices.go(new)ComputePricespure compute over wire types (conveyorget_prices, int avg)Acceptance coverage
ParseAsset/Stringround-trip; illegal symbol / wrong-decimal-count / over-MaxSatoshis error0.1 + 0.2zero-roundoff equivalent (100 + 200 = 300atoms)Price.Convertgolden vectors (incl. 128-bit intermediate1000 × 8×10^16 / 1.5×10^14 = 533333), exact integer comparison vs steemdConvertoverflow guard (result > MaxSatoshis → error)Comparecross-multiply golden;Invert().Invert()round-tripComputePricesovergo:embedtestdata fixtures → exact atom valuesTest results
Notes
STEEMUTIL_TODO.mdis a pre-existing untracked file, not part of this change.//go:build integration, realapi.steemit.com) are intentionally out of scope for this PR; deterministic unit tests + fixtures are the default suite.