mirror of
https://github.com/luxfi/graph.git
synced 2026-09-24 00:26:07 +00:00
Volume for all time is the days added up, not one window summed
A pass values a bounded slice of the swap table, newest first, because the table grows forever and rescanning it would cost more every day the chain lives. That bound is right for the pass and wrong for the field the wire calls total volume: totalling a window answers for the newest trades only. On the Lux C-Chain, with a million swaps behind it, the protocol reported the last twelve percent of its trading and called it everything. The day series already holds each day's volume, written once by the pass that saw that day and kept since. Their sum is the whole history at the cost of reading a row per day, and it grows correctly as days accrue rather than sliding forward and losing the past. The window is untouched; only what is claimed of it changes. The test gives the store three days and a window that would cover one. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -2,6 +2,7 @@ package indexer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
@@ -109,3 +110,30 @@ func TestNativeSupplySurvivesAStaleCopy(t *testing.T) {
|
||||
t.Errorf("supply = %q, want the minted 2000000000000 — a copy read before the publish landed on top", got.TotalSupply)
|
||||
}
|
||||
}
|
||||
|
||||
// Volume for all time is every day added up, not the newest window summed.
|
||||
//
|
||||
// A pass values a bounded slice of the swap table because that table grows
|
||||
// forever. The bound is right for the pass and wrong for the field the wire
|
||||
// calls total volume: on a chain with a million swaps it reported the last
|
||||
// twelve percent and called it everything. The day rows are written once and
|
||||
// kept, so their sum is the whole history at the cost of a row per day.
|
||||
func TestFactoryVolumeCountsEveryDay(t *testing.T) {
|
||||
s := newMemSQLiteStore(t)
|
||||
idx := NewWithConfig(Config{RPC: chainAt(t, 1767225600, nil).URL}, s)
|
||||
|
||||
// Three days the indexer has seen, only one of which a recent window covers.
|
||||
for i, v := range []string{"1000.00", "2000.00", "500.00"} {
|
||||
s.SetEntity(storage.FactoryDay, fmt.Sprintf("1-%d", 20000+i), map[string]interface{}{
|
||||
"id": fmt.Sprintf("1-%d", 20000+i), "date": (20000 + i) * 86400, "volumeUSD": v,
|
||||
})
|
||||
}
|
||||
|
||||
got, err := idx.factoryVolume()
|
||||
if err != nil {
|
||||
t.Fatalf("factoryVolume: %v", err)
|
||||
}
|
||||
if got != 3500 {
|
||||
t.Errorf("volume = %v, want 3500 — a day outside the window still traded", got)
|
||||
}
|
||||
}
|
||||
|
||||
+50
-1
@@ -275,11 +275,28 @@ func (idx *Indexer) revalue(parent context.Context) {
|
||||
if m, ok := f.(map[string]interface{}); ok {
|
||||
txCount = asInt64(m["txCount"])
|
||||
}
|
||||
// Volume for all time is the days added up, not one window summed.
|
||||
//
|
||||
// A pass values a bounded slice of the swap table, newest first, because the
|
||||
// table grows forever and rescanning it would cost more every day the chain
|
||||
// lives. That bound is right for the pass and wrong for this field: the wire
|
||||
// calls it total volume, and totalling a window answers for the newest
|
||||
// trades only — on a chain with a million swaps it reported the last
|
||||
// twelve percent and called it everything.
|
||||
//
|
||||
// The day series already holds each day's volume, written once and kept, so
|
||||
// the sum of those rows is the whole history at the cost of reading a row
|
||||
// per day. The window stays exactly as it is; only what is claimed of it
|
||||
// changes.
|
||||
volume := totalVol
|
||||
if allDays, err := idx.factoryVolume(); err == nil && allDays > volume {
|
||||
volume = allDays
|
||||
}
|
||||
idx.store.SeedFactory("1", &storage.SeedFactoryData{
|
||||
PoolCount: int64(len(pools)),
|
||||
TxCount: txCount,
|
||||
TotalValueLockedUSD: fmtUSD(totalTVL),
|
||||
TotalVolumeUSD: fmtUSD(totalVol),
|
||||
TotalVolumeUSD: fmtUSD(volume),
|
||||
})
|
||||
|
||||
// ── The day series: the same trades, grouped by the day they happened ──
|
||||
@@ -665,3 +682,35 @@ func fmtRatio(num, den float64) string {
|
||||
}
|
||||
return strconv.FormatFloat(num/den, 'g', 12, 64)
|
||||
}
|
||||
|
||||
// factoryVolume adds up every day the protocol has traded.
|
||||
//
|
||||
// Each row was written by the pass that saw that day and has been kept since,
|
||||
// so this is the whole history without rereading a swap. A day the indexer
|
||||
// never saw contributes nothing, which is honest: it can only report what it
|
||||
// has been shown.
|
||||
func (idx *Indexer) factoryVolume() (float64, error) {
|
||||
rows, err := idx.store.Entities(storage.FactoryDay, maxValuedPools, "date", "asc", nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
list, ok := rows.([]interface{})
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
var sum float64
|
||||
for _, r := range list {
|
||||
m, ok := r.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// The same overflow guard the rest of this pass uses (1e12): a tiny pool
|
||||
// can produce a decimal artefact that would otherwise become the headline.
|
||||
v, err := strconv.ParseFloat(fmt.Sprint(m["volumeUSD"]), 64)
|
||||
if err != nil || !(v > 0) || v > 1e12 {
|
||||
continue
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user