mirror of
https://github.com/luxfi/indexer.git
synced 2026-09-24 00:25:37 +00:00
180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
package explorer
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"math"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/luxfi/indexer/explorer/testutil"
|
|
)
|
|
|
|
func TestGiniCoefficient(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
balances []float64
|
|
wantMin float64
|
|
wantMax float64
|
|
}{
|
|
{
|
|
name: "empty",
|
|
balances: nil,
|
|
wantMin: 0, wantMax: 0,
|
|
},
|
|
{
|
|
name: "single holder",
|
|
balances: []float64{1000},
|
|
wantMin: 0, wantMax: 0.01,
|
|
},
|
|
{
|
|
name: "perfect equality",
|
|
balances: []float64{100, 100, 100, 100},
|
|
wantMin: 0, wantMax: 0.01,
|
|
},
|
|
{
|
|
name: "high inequality",
|
|
balances: []float64{1, 1, 1, 1, 1, 1, 1, 1, 1, 9991},
|
|
wantMin: 0.8, wantMax: 1.0,
|
|
},
|
|
{
|
|
name: "moderate inequality",
|
|
balances: []float64{10, 20, 30, 40, 50, 60, 70, 80, 90, 100},
|
|
wantMin: 0.2, wantMax: 0.4,
|
|
},
|
|
{
|
|
name: "zero sum",
|
|
balances: []float64{0, 0, 0},
|
|
wantMin: 0, wantMax: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := giniCoefficient(tt.balances)
|
|
if g < tt.wantMin || g > tt.wantMax {
|
|
t.Errorf("giniCoefficient() = %f, want [%f, %f]", g, tt.wantMin, tt.wantMax)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQueryTokenDistribution(t *testing.T) {
|
|
tdb := testutil.NewTestDB(t)
|
|
|
|
tokenAddrBytes := testutil.RandomAddress()
|
|
|
|
// Insert holders with varying balances.
|
|
values := []string{
|
|
"50000000000000000000000", // 50% (whale)
|
|
"30000000000000000000000", // 30% (whale)
|
|
"10000000000000000000000", // 10% (large)
|
|
"5000000000000000000000", // 5% (large)
|
|
"3000000000000000000000", // 3% (large)
|
|
"1000000000000000000000", // 1% (medium)
|
|
"500000000000000000000", // 0.5% (medium)
|
|
"300000000000000000000", // 0.3% (medium)
|
|
"100000000000000000000", // 0.1% (medium)
|
|
"100000000000000000000", // 0.1% (medium)
|
|
}
|
|
|
|
for _, val := range values {
|
|
tdb.InsertTokenBalance(t, testutil.TokenBalance{
|
|
ChainID: testutil.DefaultChainID,
|
|
AddressHash: testutil.RandomAddress(),
|
|
TokenContractAddress: tokenAddrBytes,
|
|
Value: val,
|
|
BlockNumber: 1,
|
|
TokenType: "ERC-20",
|
|
})
|
|
}
|
|
|
|
ctx := context.Background()
|
|
dist, err := queryTokenDistribution(ctx, tdb.DB, "address_current_token_balances", tokenAddrBytes)
|
|
if err != nil {
|
|
t.Fatalf("queryTokenDistribution: %v", err)
|
|
}
|
|
|
|
if dist.GiniCoefficient < 0.3 || dist.GiniCoefficient > 1.0 {
|
|
t.Errorf("gini = %f, expected moderate-to-high inequality", dist.GiniCoefficient)
|
|
}
|
|
|
|
if len(dist.HolderTiers) == 0 {
|
|
t.Error("expected holder tiers")
|
|
}
|
|
|
|
// Top 10 should be 100% (only 10 holders)
|
|
if dist.Top10HoldersPct != "100.0" {
|
|
t.Errorf("top_10_holders_pct = %s, want 100.0", dist.Top10HoldersPct)
|
|
}
|
|
}
|
|
|
|
func TestQueryTokenDistribution_Empty(t *testing.T) {
|
|
tdb := testutil.NewTestDB(t)
|
|
|
|
ctx := context.Background()
|
|
dist, err := queryTokenDistribution(ctx, tdb.DB, "address_current_token_balances", "0xdeadbeef")
|
|
if err != nil {
|
|
t.Fatalf("queryTokenDistribution: %v", err)
|
|
}
|
|
|
|
if dist.GiniCoefficient != 0 {
|
|
t.Errorf("gini = %f, want 0", dist.GiniCoefficient)
|
|
}
|
|
if dist.Top10HoldersPct != "0" {
|
|
t.Errorf("top_10 = %s, want 0", dist.Top10HoldersPct)
|
|
}
|
|
}
|
|
|
|
func TestTokenDistributionEndpoint(t *testing.T) {
|
|
tdb := testutil.NewTestDB(t)
|
|
|
|
tokenAddr := testutil.RandomHexAddress()
|
|
for _, val := range []string{"1000", "2000", "3000", "4000"} {
|
|
tdb.InsertTokenBalance(t, testutil.TokenBalance{
|
|
ChainID: testutil.DefaultChainID,
|
|
AddressHash: testutil.RandomAddress(),
|
|
TokenContractAddress: hexToBytes(tokenAddr),
|
|
Value: val,
|
|
BlockNumber: 1,
|
|
TokenType: "ERC-20",
|
|
})
|
|
}
|
|
|
|
srv, err := NewStandaloneServer(Config{
|
|
IndexerDBPath: tdb.Path,
|
|
ChainID: testutil.DefaultChainID,
|
|
ChainName: "Test",
|
|
CoinSymbol: "TST",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewStandaloneServer: %v", err)
|
|
}
|
|
t.Cleanup(func() { srv.Close() })
|
|
ts := httptest.NewServer(srv.Handler())
|
|
t.Cleanup(func() { ts.Close() })
|
|
|
|
resp, err := http.Get(ts.URL + "/v1/explorer/tokens/" + tokenAddr + "/distribution")
|
|
if err != nil {
|
|
t.Fatalf("GET: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
|
}
|
|
|
|
var dist distributionResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&dist); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
|
|
if dist.GiniCoefficient < 0 || dist.GiniCoefficient > 1 {
|
|
t.Errorf("gini = %f, out of range", dist.GiniCoefficient)
|
|
}
|
|
if math.IsNaN(dist.GiniCoefficient) {
|
|
t.Error("gini is NaN")
|
|
}
|
|
}
|