mirror of
https://github.com/luxfi/graph.git
synced 2026-09-24 00:26:07 +00:00
The coin's supply is what was minted, not what sits in the wrapper
A chain's own coin has no contract, so its minted supply, its treasury and its stake are published onto the wrapped native's row — the entity a token page opens for LUX. That write went to the store only, and the pass that makes it reads every token into memory at the top and writes those copies back near the end. The published figure landed inside that window and a copy read before it came down on top. So the coin reported whatever its wrapper happens to hold wrapped. Zoo's two trillion showed as the fifteen billion sitting in the contract, and its market cap and fully diluted value were both wrong by a factor of 133 — while the indexer's own log said "2000000000000 minted" every thirty seconds. The publish now corrects the copy the pass is holding as well as the row on disk, so the answer is the same whichever write lands last, and it happens after the token rows rather than before them. Lux was right only by accident: its row already held the minted figure, so the stale copy wrote the same number back. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+15
-2
@@ -6,6 +6,8 @@ import (
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
// What a native token is worth depends on three numbers, and they live in three
|
||||
@@ -134,7 +136,15 @@ func (idx *Indexer) platformCall(ctx context.Context, url, method string, params
|
||||
// Written each pass rather than once: the treasury balance falls as tokens are
|
||||
// distributed and the staked figure moves with every delegation, so a value
|
||||
// read at boot would be quietly stale within the hour.
|
||||
func (idx *Indexer) publishNativeSupply(ctx context.Context) {
|
||||
//
|
||||
// It writes to the pass's own copy of the row as well as to the store, and that
|
||||
// is not belt-and-braces. The pass reads every token into memory at the top and
|
||||
// writes those copies back near the end, so a store-only write sits inside a
|
||||
// window where a copy read before it lands on top — the coin then reports
|
||||
// whatever its wrapper holds wrapped, and Zoo's two trillion showed as the
|
||||
// fifteen billion sitting in the contract. Correcting the copy the pass already
|
||||
// holds makes the result the same whichever runs last.
|
||||
func (idx *Indexer) publishNativeSupply(ctx context.Context, inFlight map[string]*storage.SeedTokenData) {
|
||||
if idx.native == "" {
|
||||
return
|
||||
}
|
||||
@@ -142,7 +152,10 @@ func (idx *Indexer) publishNativeSupply(ctx context.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tok := idx.store.TokensRaw()[idx.native]
|
||||
tok := inFlight[idx.native]
|
||||
if tok == nil {
|
||||
tok = idx.store.TokensRaw()[idx.native]
|
||||
}
|
||||
if tok == nil {
|
||||
return
|
||||
}
|
||||
|
||||
+42
-1
@@ -1,6 +1,11 @@
|
||||
package indexer
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
// Circulating is what is left after everything that exists but cannot be sold:
|
||||
// what the treasury has not distributed, and what is bonded to a validator. It
|
||||
@@ -68,3 +73,39 @@ func TestUnitsAreWrittenPlainly(t *testing.T) {
|
||||
t.Errorf("fmtUnits = %q, want 2000000000000", got)
|
||||
}
|
||||
}
|
||||
|
||||
// The coin's minted supply survives a copy of its row written after the publish.
|
||||
//
|
||||
// The valuation pass reads every token into memory at the top and writes those
|
||||
// copies back near the end. A supply written only to the store sits inside that
|
||||
// window: the copy read before it lands on top, and the chain's own coin then
|
||||
// reports whatever its wrapper holds wrapped — Zoo's two trillion showed as the
|
||||
// fifteen billion sitting in the contract.
|
||||
func TestNativeSupplySurvivesAStaleCopy(t *testing.T) {
|
||||
const wrapper = "0x4888e4a2ee0f03051c72d2bd3acf755ed3498b3e"
|
||||
s := newMemSQLiteStore(t)
|
||||
// The wrapper as the chain reports it: what has actually been wrapped.
|
||||
s.SeedToken(wrapper, &storage.SeedTokenData{
|
||||
Symbol: "WLUX", Name: "Wrapped LUX", Decimals: 18, TotalSupply: "15026903324",
|
||||
})
|
||||
idx := NewWithConfig(Config{
|
||||
RPC: chainAt(t, 1767225600, nil).URL,
|
||||
Native: wrapper,
|
||||
GenesisSupply: "2000000000000",
|
||||
}, s)
|
||||
|
||||
// What the pass does: read the rows, publish the supply, write the rows back.
|
||||
inFlight := s.TokensRaw()
|
||||
idx.publishNativeSupply(context.Background(), inFlight)
|
||||
for addr, t := range inFlight {
|
||||
s.SeedToken(addr, t)
|
||||
}
|
||||
|
||||
got := s.TokensRaw()[wrapper]
|
||||
if got == nil {
|
||||
t.Fatal("the wrapper's row vanished")
|
||||
}
|
||||
if got.TotalSupply != "2000000000000" {
|
||||
t.Errorf("supply = %q, want the minted 2000000000000 — a copy read before the publish landed on top", got.TotalSupply)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -173,7 +173,6 @@ func (idx *Indexer) revalue(parent context.Context) {
|
||||
// Unpriced stays unpriced: no anchor means no bundle and no derivedETH,
|
||||
// never a zero standing in for a number nobody knows.
|
||||
nativeUSD := idx.publishAnchor(prices)
|
||||
idx.publishNativeSupply(ctx)
|
||||
|
||||
// ── Pools: TVL, spot prices, volume ────────────────────────────────
|
||||
//
|
||||
@@ -255,6 +254,17 @@ func (idx *Indexer) revalue(parent context.Context) {
|
||||
idx.store.SeedToken(addr, t)
|
||||
}
|
||||
|
||||
// After the token rows, not before them.
|
||||
//
|
||||
// This pass reads every token into memory once at the top and writes those
|
||||
// copies back here. Run earlier, the supply landed in the store and was then
|
||||
// overwritten by a copy read before it — so the chain's own coin reported
|
||||
// whatever its wrapper's ERC20 says it has wrapped, and Zoo's two trillion
|
||||
// showed as the fifteen billion sitting in the contract. The write has to be
|
||||
// the last word on the row, because it is the only one that knows what was
|
||||
// minted.
|
||||
idx.publishNativeSupply(ctx, tokens)
|
||||
|
||||
// ── Factory: the landing-page header aggregate ─────────────────────
|
||||
// poolCount is DERIVED here (the true number of pools in the store), not
|
||||
// accumulated by the create handlers — an accumulator drifts on every
|
||||
|
||||
Reference in New Issue
Block a user