PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
feeledger
Namespace
g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Files
3 (gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger
gno
0.9

Files (3)

  • gnomod.tomltoml
  • feeledger.gnogno
  • feeledger_test.gnogno
feeledger_test.gnogno
1package feeledger23import "testing"45const (6	maxInt64 = int64(1<<63 - 1)7	minInt64 = int64(-1 << 63)8)910// mustPanicWith asserts fn panics with an error whose message is want.11// Non-crossing panics in the same package are recoverable.12func mustPanicWith(t *testing.T, want string, fn func()) {13	t.Helper()14	defer func() {15		r := recover()16		if r == nil {17			t.Fatalf("expected panic %q, got none", want)18		}19		err, ok := r.(error)20		if !ok || err.Error() != want {21			t.Fatalf("panic = %v, want %q", r, want)22		}23	}()24	fn()25}2627func TestNewBounds(t *testing.T) {28	for _, bad := range []int64{-1, BpsDenominator + 1, minInt64, maxInt64} {29		if _, err := New(bad); err != ErrInvalidBps {30			t.Fatalf("New(%d) err = %v, want ErrInvalidBps", bad, err)31		}32	}33	for _, ok := range []int64{0, 1, BpsDenominator} {34		if _, err := New(ok); err != nil {35			t.Fatalf("New(%d) err = %v", ok, err)36		}37	}38	mustPanicWith(t, ErrInvalidBps.Error(), func() { MustNew(-1) })39}4041func TestFeeForRounding(t *testing.T) {42	// floor(max * 0.9999) computed the same overflow-free way feeFor does.43	wantMaxAt9999 := maxInt64/10000*9999 + maxInt64%10000*9999/1000044	cases := []struct {45		amount, bps, want int6446	}{47		{999, 100, 9},      // 9.99 floors to 948		{10000, 1, 1},      // exact49		{9999, 1, 0},       // 0.9999 floors to 0 — no minimum fee50		{1, 9999, 0},       // 0.9999 floors to 051		{10, 9999, 9},      // 9.999 floors to 952		{100, 10000, 100},  // full-fee bps: fee == amount53		{0, 500, 0},        // zero amount previews zero fee54		{1000, 0, 0},       // zero-fee config55		{1000, 250, 25},    // exact 2.5%56		{999, 250, 24},     // 24.975 floors to 2457		{maxInt64, 10000, maxInt64}, // no intermediate overflow58		{maxInt64, 9999, wantMaxAt9999},59	}60	for _, c := range cases {61		got, err := FeeFor(c.amount, c.bps)62		if err != nil {63			t.Fatalf("FeeFor(%d,%d) err = %v", c.amount, c.bps, err)64		}65		if got != c.want {66			t.Fatalf("FeeFor(%d,%d) = %d, want %d", c.amount, c.bps, got, c.want)67		}68		if got < 0 || got > c.amount {69			t.Fatalf("FeeFor(%d,%d) = %d out of [0,amount]", c.amount, c.bps, got)70		}71	}72	if _, err := FeeFor(-1, 100); err != ErrInvalidAmount {73		t.Fatalf("FeeFor(-1,100) err = %v, want ErrInvalidAmount", err)74	}75	if _, err := FeeFor(100, -1); err != ErrInvalidBps {76		t.Fatalf("FeeFor(100,-1) err = %v, want ErrInvalidBps", err)77	}78	if _, err := FeeFor(100, BpsDenominator+1); err != ErrInvalidBps {79		t.Fatalf("FeeFor over-bps err = %v, want ErrInvalidBps", err)80	}81}8283func TestDepositValidation(t *testing.T) {84	l := MustNew(500)85	type bad struct {86		account     string87		amount, bps int6488		want        error89	}90	for _, c := range []bad{91		{"", 100, 0, ErrEmptyAccount},92		{"alice", 0, 0, ErrInvalidAmount},93		{"alice", -7, 0, ErrInvalidAmount},94		{"alice", minInt64, 0, ErrInvalidAmount},95		{"alice", 100, -1, ErrInvalidBps},96		{"alice", 100, 501, ErrInvalidBps}, // above the ledger cap97	} {98		if _, _, err := l.Deposit(c.account, c.amount, c.bps); err != c.want {99			t.Fatalf("Deposit(%q,%d,%d) err = %v, want %v", c.account, c.amount, c.bps, err, c.want)100		}101	}102	if l.UsersTotal() != 0 || l.FeesAccrued() != 0 || l.Accounts() != 0 {103		t.Fatal("failed deposits mutated state")104	}105}106107func TestDepositAndAccumulate(t *testing.T) {108	l := MustNew(1000)109	credited, fee, err := l.Deposit("alice", 1000, 250)110	if err != nil || credited != 975 || fee != 25 {111		t.Fatalf("Deposit = (%d,%d,%v), want (975,25,nil)", credited, fee, err)112	}113	credited, fee, err = l.Deposit("alice", 999, 250)114	if err != nil || credited != 975 || fee != 24 { // fee floors, credit rounds up115		t.Fatalf("Deposit#2 = (%d,%d,%v), want (975,24,nil)", credited, fee, err)116	}117	if got := l.BalanceOf("alice"); got != 1950 {118		t.Fatalf("BalanceOf = %d, want 1950", got)119	}120	if l.UsersTotal() != 1950 || l.FeesAccrued() != 49 || l.Liabilities() != 1999 {121		t.Fatalf("totals = %d/%d/%d", l.UsersTotal(), l.FeesAccrued(), l.Liabilities())122	}123	// credited + fee always reassembles both deposits exactly.124	if l.Liabilities() != 1000+999 {125		t.Fatal("conservation broken: credited+fee != deposited")126	}127}128129func TestFullFeeBpsCreditsZero(t *testing.T) {130	l := MustNew(BpsDenominator)131	credited, fee, err := l.Deposit("alice", 777, BpsDenominator)132	if err != nil || credited != 0 || fee != 777 {133		t.Fatalf("Deposit = (%d,%d,%v), want (0,777,nil)", credited, fee, err)134	}135	if l.Accounts() != 0 || l.BalanceOf("alice") != 0 {136		t.Fatal("zero-credit deposit must not create an account entry")137	}138	if l.FeesAccrued() != 777 || l.UsersTotal() != 0 {139		t.Fatal("fee pot mismatch on full-fee deposit")140	}141}142143func TestDepositOverflow(t *testing.T) {144	l := MustNew(BpsDenominator)145	if _, _, err := l.Deposit("alice", maxInt64, 0); err != nil {146		t.Fatalf("seed deposit err = %v", err)147	}148	// Account balance would overflow.149	if _, _, err := l.Deposit("alice", 1, 0); err != ErrOverflow {150		t.Fatalf("balance-overflow err = %v, want ErrOverflow", err)151	}152	// Joint liabilities (users + fees) would overflow even though the153	// fee pot alone would not.154	if _, _, err := l.Deposit("bob", 100, BpsDenominator); err != ErrOverflow {155		t.Fatalf("liabilities-overflow err = %v, want ErrOverflow", err)156	}157	if l.BalanceOf("bob") != 0 || l.FeesAccrued() != 0 || l.UsersTotal() != maxInt64 {158		t.Fatal("failed overflow deposits mutated state")159	}160}161162func TestWithdraw(t *testing.T) {163	l := MustNew(0)164	l.MustDeposit("alice", 1000, 0)165166	if err := l.Withdraw("", 10); err != ErrEmptyAccount {167		t.Fatalf("empty account err = %v", err)168	}169	if err := l.Withdraw("alice", 0); err != ErrInvalidAmount {170		t.Fatalf("zero amount err = %v", err)171	}172	if err := l.Withdraw("alice", -3); err != ErrInvalidAmount {173		t.Fatalf("negative amount err = %v", err)174	}175	if err := l.Withdraw("alice", 1001); err != ErrInsufficient {176		t.Fatalf("over-balance err = %v", err)177	}178	if err := l.Withdraw("ghost", 1); err != ErrInsufficient {179		t.Fatalf("absent account err = %v", err)180	}181	if l.BalanceOf("alice") != 1000 || l.UsersTotal() != 1000 {182		t.Fatal("failed withdrawals mutated state")183	}184185	if err := l.Withdraw("alice", 400); err != nil {186		t.Fatalf("partial withdraw err = %v", err)187	}188	if l.BalanceOf("alice") != 600 || l.UsersTotal() != 600 || l.Accounts() != 1 {189		t.Fatal("partial withdraw wrong state")190	}191	if err := l.Withdraw("alice", 600); err != nil {192		t.Fatalf("exact withdraw err = %v", err)193	}194	if l.BalanceOf("alice") != 0 || l.UsersTotal() != 0 || l.Accounts() != 0 {195		t.Fatal("exact withdraw must remove the account entry")196	}197	if err := l.Withdraw("alice", 1); err != ErrInsufficient {198		t.Fatalf("repeat withdraw err = %v, want ErrInsufficient", err)199	}200	mustPanicWith(t, ErrInsufficient.Error(), func() { l.MustWithdraw("alice", 1) })201}202203func TestWithdrawAll(t *testing.T) {204	l := MustNew(0)205	if got, err := l.WithdrawAll("nobody"); err != nil || got != 0 {206		t.Fatalf("WithdrawAll(nobody) = (%d,%v), want (0,nil)", got, err)207	}208	if _, err := l.WithdrawAll(""); err != ErrEmptyAccount {209		t.Fatalf("WithdrawAll(\"\") err = %v", err)210	}211	l.MustDeposit("alice", 555, 0)212	if got, err := l.WithdrawAll("alice"); err != nil || got != 555 {213		t.Fatalf("WithdrawAll = (%d,%v), want (555,nil)", got, err)214	}215	if got, err := l.WithdrawAll("alice"); err != nil || got != 0 {216		t.Fatalf("repeat WithdrawAll = (%d,%v), want (0,nil)", got, err)217	}218	if l.UsersTotal() != 0 || l.Accounts() != 0 {219		t.Fatal("WithdrawAll left state behind")220	}221}222223func TestWithdrawFees(t *testing.T) {224	l := MustNew(1000)225	l.MustDeposit("alice", 10000, 1000) // fee 1000226	l.MustDeposit("bob", 10000, 1000)   // fee 1000227	if got := l.WithdrawFees(); got != 2000 {228		t.Fatalf("WithdrawFees = %d, want 2000", got)229	}230	if got := l.WithdrawFees(); got != 0 {231		t.Fatalf("repeat WithdrawFees = %d, want 0", got)232	}233	if l.FeesAccrued() != 0 || l.UsersTotal() != 18000 {234		t.Fatal("WithdrawFees corrupted totals")235	}236}237238func TestConservationAcrossSequence(t *testing.T) {239	l := MustNew(1000)240	deposited := int64(0)241	withdrawn := int64(0)242	feesOut := int64(0)243244	step := func() {245		t.Helper()246		// Internal consistency: usersTotal == sum of balances.247		sum := int64(0)248		l.Iterate(func(_ string, b int64) bool {249			if b <= 0 {250				t.Fatal("zero/negative balance stored")251			}252			sum += b253			return false254		})255		if sum != l.UsersTotal() {256			t.Fatalf("usersTotal %d != Σ balances %d", l.UsersTotal(), sum)257		}258		// Conservation: everything in == everything owed + everything out.259		if deposited != l.Liabilities()+withdrawn+feesOut {260			t.Fatalf("conservation: in=%d owed=%d out=%d+%d",261				deposited, l.Liabilities(), withdrawn, feesOut)262		}263	}264265	deposit := func(who string, amt, bps int64) {266		l.MustDeposit(who, amt, bps)267		deposited += amt268		step()269	}270	claim := func(who string, amt int64) {271		l.MustWithdraw(who, amt)272		withdrawn += amt273		step()274	}275276	deposit("alice", 1000, 0)277	deposit("bob", 999, 250)278	deposit("alice", 12345, 1000)279	claim("alice", 1000)280	deposit("carol", 1, 1000) // fee floors to 0281	feesOut += l.WithdrawFees()282	step()283	claim("bob", 974)284	all, _ := l.WithdrawAll("alice")285	withdrawn += all286	step()287	deposit("dave", 7, 999)288	feesOut += l.WithdrawFees()289	step()290}291292func TestCheckedAddBranches(t *testing.T) {293	cases := []struct {294		a, b, want int64295		ok         bool296	}{297		{5, 3, 8, true},298		{5, -3, 2, true},   // negative-b branch, no overflow299		{-4, -6, -10, true},300		{maxInt64, 1, 0, false},  // positive overflow301		{minInt64, -1, 0, false}, // negative overflow302		{maxInt64, -maxInt64, 0, true},303	}304	for _, c := range cases {305		got, ok := checkedAdd(c.a, c.b)306		if ok != c.ok || (ok && got != c.want) {307			t.Fatalf("checkedAdd(%d,%d) = (%d,%v), want (%d,%v)", c.a, c.b, got, ok, c.want, c.ok)308		}309	}310}311312func TestIterateSortedAndEarlyStop(t *testing.T) {313	l := MustNew(0)314	l.MustDeposit("carol", 3, 0)315	l.MustDeposit("alice", 1, 0)316	l.MustDeposit("bob", 2, 0)317	got := ""318	l.Iterate(func(a string, _ int64) bool {319		got += a + ","320		return false321	})322	if got != "alice,bob,carol," {323		t.Fatalf("Iterate order = %q", got)324	}325	n := 0326	l.Iterate(func(string, int64) bool { n++; return n == 2 })327	if n != 2 {328		t.Fatalf("early stop visited %d", n)329	}330}331

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.