1package fee_split23import (4 "chain"5 "chain/banker"6 "strconv"7 "strings"8 "testing"910 "gno.land/p/nt/testutils/v0"11 "gno.land/p/nt/uassert/v0"12)1314var (15 owner = testutils.TestAddress("owner")16 alice = testutils.TestAddress("alice")17 bob = testutils.TestAddress("bob")18 carol = testutils.TestAddress("carol")19 dave = testutils.TestAddress("dave")20 attacker = testutils.TestAddress("attacker")21)2223// resetState resets all package-level state between tests.24// Required because Gno test functions share the same VM context.25func resetState() {26 splits = make(map[string]*Split)27 splitIDs = []string{}28 ownerSplits = make(map[address]int)29 nextID = 130 feeBps = 031 feeAdmin = feeAdminAddr32 pendingFeeAdmin = ""33 feesAccrued = 034 feesClaimed = 035}3637var feeAdminAddr = testutils.TestAddress("feeadmin")3839func addrList(addrs ...address) string {40 parts := make([]string, len(addrs))41 for i, a := range addrs {42 parts[i] = string(a)43 }44 return strings.Join(parts, ",")45}4647// fund gives the realm's package address coins so Claim payouts succeed,48// mirroring the deposits the accounting records.49func fund(amount int64) {50 pkgAddr := chain.PackageAddress("gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/fee_split/v2")51 testing.IssueCoins(pkgAddr, chain.Coins{{Denom: Denom, Amount: amount}})52}5354// deposit performs a coin-backed deposit as addr.55func deposit(cur realm, addr address, splitID string, amount int64) {56 testing.SetRealm(testing.NewUserRealm(addr))57 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: amount}})58 fund(amount)59 Deposit(cross(cur), splitID)60 testing.SetOriginSend(nil)61}6263// ---------- CreateSplit ----------6465func TestCreateSplit_Basic(cur realm, t *testing.T) {66 resetState()67 testing.SetRealm(testing.NewUserRealm(owner))6869 id := CreateSplit(cross(cur), addrList(alice, bob), "6000,4000")70 uassert.Equal(t, "split_1", id)7172 s := splits["split_1"]73 uassert.Equal(t, string(owner), string(s.Owner))74 uassert.Equal(t, 2, len(s.Recipients))75 uassert.Equal(t, string(alice), string(s.Recipients[0]))76 uassert.Equal(t, string(bob), string(s.Recipients[1]))77 uassert.Equal(t, int64(6000), s.Shares[0])78 uassert.Equal(t, int64(4000), s.Shares[1])79 uassert.False(t, s.Frozen, "new split must not be frozen")80 uassert.Equal(t, int64(0), s.TotalDeposited)81 uassert.Equal(t, int64(0), s.TotalClaimed)82}8384func TestCreateSplit_IDsIncrement(cur realm, t *testing.T) {85 resetState()86 testing.SetRealm(testing.NewUserRealm(owner))8788 id1 := CreateSplit(cross(cur), addrList(alice), "10000")89 id2 := CreateSplit(cross(cur), addrList(bob), "10000")90 uassert.Equal(t, "split_1", id1)91 uassert.Equal(t, "split_2", id2)92}9394func TestCreateSplit_DuplicateRecipient(cur realm, t *testing.T) {95 resetState()96 testing.SetRealm(testing.NewUserRealm(owner))9798 uassert.AbortsWithMessage(t, cur,99 "duplicate recipient: "+string(alice),100 func() { CreateSplit(cross(cur), addrList(alice, alice), "5000,5000") },101 )102}103104func TestCreateSplit_SharesDontSum(cur realm, t *testing.T) {105 resetState()106 testing.SetRealm(testing.NewUserRealm(owner))107108 uassert.AbortsWithMessage(t, cur,109 "shares must sum to 10000 basis points, got 5000",110 func() { CreateSplit(cross(cur), addrList(alice), "5000") },111 )112}113114func TestCreateSplit_LengthMismatch(cur realm, t *testing.T) {115 resetState()116 testing.SetRealm(testing.NewUserRealm(owner))117118 uassert.AbortsWithMessage(t, cur,119 "recipients and shares must have the same length",120 func() { CreateSplit(cross(cur), addrList(alice, bob), "10000") },121 )122}123124func TestCreateSplit_InvalidAddressRejected(cur realm, t *testing.T) {125 resetState()126 testing.SetRealm(testing.NewUserRealm(owner))127128 uassert.AbortsWithMessage(t, cur,129 "invalid recipient address: not_an_address",130 func() { CreateSplit(cross(cur), "not_an_address", "10000") },131 )132}133134func TestCreateSplit_PerOwnerLimit(cur realm, t *testing.T) {135 resetState()136 testing.SetRealm(testing.NewUserRealm(owner))137138 for i := 0; i < MaxSplitsPerOwner; i++ {139 CreateSplit(cross(cur), addrList(alice), "10000")140 }141 uassert.AbortsWithMessage(t, cur,142 "per-owner split limit reached",143 func() { CreateSplit(cross(cur), addrList(alice), "10000") },144 )145}146147// ---------- Deposit (coin-backed) ----------148149func TestDeposit_UsesSentCoins(cur realm, t *testing.T) {150 resetState()151 testing.SetRealm(testing.NewUserRealm(owner))152 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")153154 deposit(cur, owner, id, 1000)155156 s := splits[id]157 uassert.Equal(t, int64(1000), s.TotalDeposited)158 uassert.Equal(t, int64(500), s.Balances[alice])159 uassert.Equal(t, int64(500), s.Balances[bob])160}161162func TestDeposit_NoCoinsPanics(cur realm, t *testing.T) {163 resetState()164 testing.SetRealm(testing.NewUserRealm(owner))165 id := CreateSplit(cross(cur), addrList(alice), "10000")166167 // no SetOriginSend: nothing attached to the call168 testing.SetOriginSend(nil)169 uassert.AbortsWithMessage(t, cur,170 "deposit must send exactly one coin of "+Denom,171 func() { Deposit(cross(cur), id) },172 )173 uassert.Equal(t, int64(0), splits[id].TotalDeposited)174}175176func TestDeposit_WrongDenomPanics(cur realm, t *testing.T) {177 resetState()178 testing.SetRealm(testing.NewUserRealm(owner))179 id := CreateSplit(cross(cur), addrList(alice), "10000")180181 testing.SetOriginSend(chain.Coins{{Denom: "ufoo", Amount: 100}})182 uassert.AbortsWithMessage(t, cur,183 "deposit must send exactly one coin of "+Denom,184 func() { Deposit(cross(cur), id) },185 )186 testing.SetOriginSend(nil)187}188189func TestDeposit_RoundingDustGoesToHighestShare(cur realm, t *testing.T) {190 resetState()191 testing.SetRealm(testing.NewUserRealm(owner))192 id := CreateSplit(cross(cur), addrList(alice, bob, carol), "3334,3333,3333")193194 deposit(cur, owner, id, 10)195196 s := splits[id]197 uassert.Equal(t, int64(10), s.TotalDeposited)198 // alice: (10*3334)/10000 = 3, +1 dust (highest share)199 uassert.Equal(t, int64(4), s.Balances[alice])200 uassert.Equal(t, int64(3), s.Balances[bob])201 uassert.Equal(t, int64(3), s.Balances[carol])202}203204func TestDeposit_AccumulatesAcrossMultipleDeposits(cur realm, t *testing.T) {205 resetState()206 testing.SetRealm(testing.NewUserRealm(owner))207 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")208209 deposit(cur, owner, id, 100)210 deposit(cur, owner, id, 200)211212 s := splits[id]213 uassert.Equal(t, int64(300), s.TotalDeposited)214 uassert.Equal(t, int64(150), s.Balances[alice])215 uassert.Equal(t, int64(150), s.Balances[bob])216}217218func TestDeposit_OverflowGuard(cur realm, t *testing.T) {219 resetState()220 testing.SetRealm(testing.NewUserRealm(owner))221 id := CreateSplit(cross(cur), addrList(alice), "10000")222223 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: MaxDepositAmount + 1}})224 uassert.AbortsWithMessage(t, cur,225 "deposit exceeds maximum supported amount",226 func() { Deposit(cross(cur), id) },227 )228 testing.SetOriginSend(nil)229 uassert.Equal(t, int64(0), splits[id].TotalDeposited)230}231232func TestDeposit_FrozenSplitPanics(cur realm, t *testing.T) {233 resetState()234 testing.SetRealm(testing.NewUserRealm(owner))235 id := CreateSplit(cross(cur), addrList(alice), "10000")236 Freeze(cross(cur), id)237238 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: 100}})239 uassert.AbortsWithMessage(t, cur,240 "split is frozen",241 func() { Deposit(cross(cur), id) },242 )243 testing.SetOriginSend(nil)244}245246func TestDeposit_UnknownSplitPanics(cur realm, t *testing.T) {247 resetState()248 testing.SetRealm(testing.NewUserRealm(owner))249250 uassert.AbortsWithMessage(t, cur,251 "split not found: split_99",252 func() { Deposit(cross(cur), "split_99") },253 )254}255256// ---------- Claim (pays out coins) ----------257258func TestClaim_SendsCoins(cur realm, t *testing.T) {259 resetState()260 testing.SetRealm(testing.NewUserRealm(owner))261 id := CreateSplit(cross(cur), addrList(alice, bob), "7000,3000")262 deposit(cur, owner, id, 10000)263264 before := banker.NewReadonlyBanker().GetCoins(alice).AmountOf(Denom)265266 testing.SetRealm(testing.NewUserRealm(alice))267 claimed := Claim(cross(cur), id)268 uassert.Equal(t, int64(7000), claimed)269270 after := banker.NewReadonlyBanker().GetCoins(alice).AmountOf(Denom)271 uassert.Equal(t, int64(7000), after-before)272273 s := splits[id]274 uassert.Equal(t, int64(0), s.Balances[alice])275 uassert.Equal(t, int64(7000), s.TotalClaimed)276}277278func TestClaim_CallerIdentityIsEnforced(cur realm, t *testing.T) {279 resetState()280 testing.SetRealm(testing.NewUserRealm(owner))281 id := CreateSplit(cross(cur), addrList(alice), "10000")282 deposit(cur, owner, id, 100)283284 // the attacker cannot claim alice's balance: identity comes from the285 // chain, not from an argument286 testing.SetRealm(testing.NewUserRealm(attacker))287 uassert.AbortsWithMessage(t, cur,288 "not a recipient of this split",289 func() { Claim(cross(cur), id) },290 )291 uassert.Equal(t, int64(100), splits[id].Balances[alice])292}293294func TestClaim_NothingToClaim(cur realm, t *testing.T) {295 resetState()296 testing.SetRealm(testing.NewUserRealm(owner))297 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")298299 testing.SetRealm(testing.NewUserRealm(alice))300 uassert.AbortsWithMessage(t, cur,301 "nothing to claim",302 func() { Claim(cross(cur), id) },303 )304}305306func TestClaim_CannotClaimTwice(cur realm, t *testing.T) {307 resetState()308 testing.SetRealm(testing.NewUserRealm(owner))309 id := CreateSplit(cross(cur), addrList(alice), "10000")310 deposit(cur, owner, id, 100)311312 testing.SetRealm(testing.NewUserRealm(alice))313 Claim(cross(cur), id)314 uassert.AbortsWithMessage(t, cur,315 "nothing to claim",316 func() { Claim(cross(cur), id) },317 )318}319320func TestClaim_ExRecipientKeepsAccruedBalance(cur realm, t *testing.T) {321 resetState()322 testing.SetRealm(testing.NewUserRealm(owner))323 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")324 deposit(cur, owner, id, 1000)325326 testing.SetRealm(testing.NewUserRealm(owner))327 UpdateShares(cross(cur), id, addrList(carol, dave), "6000,4000")328329 // bob was removed but keeps — and can claim — his accrued 500330 testing.SetRealm(testing.NewUserRealm(bob))331 claimed := Claim(cross(cur), id)332 uassert.Equal(t, int64(500), claimed)333}334335// ---------- UpdateShares ----------336337func TestUpdateShares_Basic(cur realm, t *testing.T) {338 resetState()339 testing.SetRealm(testing.NewUserRealm(owner))340 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")341342 UpdateShares(cross(cur), id, addrList(alice, carol), "7000,3000")343344 s := splits[id]345 uassert.Equal(t, 2, len(s.Recipients))346 uassert.Equal(t, string(alice), string(s.Recipients[0]))347 uassert.Equal(t, string(carol), string(s.Recipients[1]))348 uassert.Equal(t, int64(7000), s.Shares[0])349 uassert.Equal(t, int64(3000), s.Shares[1])350}351352func TestUpdateShares_PreservesUnclaimedBalances(cur realm, t *testing.T) {353 resetState()354 testing.SetRealm(testing.NewUserRealm(owner))355 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")356 deposit(cur, owner, id, 1000)357358 testing.SetRealm(testing.NewUserRealm(owner))359 UpdateShares(cross(cur), id, addrList(carol, dave), "6000,4000")360361 s := splits[id]362 uassert.Equal(t, int64(500), s.Balances[alice])363 uassert.Equal(t, int64(500), s.Balances[bob])364}365366func TestUpdateShares_WrongOwner(cur realm, t *testing.T) {367 resetState()368 testing.SetRealm(testing.NewUserRealm(owner))369 id := CreateSplit(cross(cur), addrList(alice), "10000")370371 testing.SetRealm(testing.NewUserRealm(attacker))372 uassert.AbortsWithMessage(t, cur,373 "only the owner can update shares",374 func() { UpdateShares(cross(cur), id, addrList(alice), "10000") },375 )376}377378func TestUpdateShares_FrozenSplitPanics(cur realm, t *testing.T) {379 resetState()380 testing.SetRealm(testing.NewUserRealm(owner))381 id := CreateSplit(cross(cur), addrList(alice), "10000")382 Freeze(cross(cur), id)383384 uassert.AbortsWithMessage(t, cur,385 "split is frozen",386 func() { UpdateShares(cross(cur), id, addrList(alice, bob), "5000,5000") },387 )388}389390// ---------- TransferOwnership / AcceptOwnership (two-step, v2 Y1) ----------391392func TestTransferOwnership_TwoStep(cur realm, t *testing.T) {393 resetState()394 testing.SetRealm(testing.NewUserRealm(owner))395 id := CreateSplit(cross(cur), addrList(alice), "10000")396397 TransferOwnership(cross(cur), id, bob)398 // staging moves NOTHING: owner, both quotas unchanged399 uassert.Equal(t, string(owner), string(splits[id].Owner))400 uassert.Equal(t, string(bob), string(splits[id].PendingOwner))401 uassert.Equal(t, 1, ownerSplits[owner])402 uassert.Equal(t, 0, ownerSplits[bob])403404 testing.SetRealm(testing.NewUserRealm(bob))405 AcceptOwnership(cross(cur), id)406 uassert.Equal(t, string(bob), string(splits[id].Owner))407 uassert.Equal(t, "", string(splits[id].PendingOwner))408}409410func TestTransferOwnership_WrongOwner(cur realm, t *testing.T) {411 resetState()412 testing.SetRealm(testing.NewUserRealm(owner))413 id := CreateSplit(cross(cur), addrList(alice), "10000")414415 testing.SetRealm(testing.NewUserRealm(attacker))416 uassert.AbortsWithMessage(t, cur,417 "only the owner can transfer ownership",418 func() { TransferOwnership(cross(cur), id, attacker) },419 )420}421422func TestAcceptOwnership_OnlyNominee(cur realm, t *testing.T) {423 resetState()424 testing.SetRealm(testing.NewUserRealm(owner))425 id := CreateSplit(cross(cur), addrList(alice), "10000")426 TransferOwnership(cross(cur), id, bob)427428 // a non-nominee cannot accept — including with no nomination staged429 testing.SetRealm(testing.NewUserRealm(attacker))430 uassert.AbortsWithMessage(t, cur,431 "caller is not the pending owner",432 func() { AcceptOwnership(cross(cur), id) },433 )434 // the owner clears the nomination; the ex-nominee can no longer accept435 testing.SetRealm(testing.NewUserRealm(owner))436 TransferOwnership(cross(cur), id, "")437 testing.SetRealm(testing.NewUserRealm(bob))438 uassert.AbortsWithMessage(t, cur,439 "caller is not the pending owner",440 func() { AcceptOwnership(cross(cur), id) },441 )442}443444func TestAcceptOwnership_MovesOwnerSlotAtConsent(cur realm, t *testing.T) {445 resetState()446 testing.SetRealm(testing.NewUserRealm(owner))447 id := CreateSplit(cross(cur), addrList(alice), "10000")448 uassert.Equal(t, 1, ownerSplits[owner])449 TransferOwnership(cross(cur), id, bob)450451 testing.SetRealm(testing.NewUserRealm(bob))452 AcceptOwnership(cross(cur), id)453454 // the creator's slot is freed; the acceptor's is consumed455 uassert.Equal(t, 0, ownerSplits[owner])456 uassert.Equal(t, 1, ownerSplits[bob])457458 // and the creator can fill the freed slot again459 testing.SetRealm(testing.NewUserRealm(owner))460 for i := 0; i < MaxSplitsPerOwner; i++ {461 CreateSplit(cross(cur), addrList(alice), "10000")462 }463}464465func TestAcceptOwnership_AcceptorAtLimitBlocked(cur realm, t *testing.T) {466 resetState()467 testing.SetRealm(testing.NewUserRealm(bob))468 for i := 0; i < MaxSplitsPerOwner; i++ {469 CreateSplit(cross(cur), addrList(alice), "10000")470 }471472 testing.SetRealm(testing.NewUserRealm(owner))473 id := CreateSplit(cross(cur), addrList(alice), "10000")474 TransferOwnership(cross(cur), id, bob)475476 // the quota gate fires at ACCEPT, against the acceptor's own count477 testing.SetRealm(testing.NewUserRealm(bob))478 uassert.AbortsWithMessage(t, cur,479 "new owner is at the per-owner split limit",480 func() { AcceptOwnership(cross(cur), id) },481 )482}483484// The v2 audit Y1 griefing scenario is dead: staging a transfer at a485// victim consumes none of the victim's quota and changes nothing the486// victim must act on.487func TestTransferOwnership_UnconsentedGriefDefused(cur realm, t *testing.T) {488 resetState()489 testing.SetRealm(testing.NewUserRealm(attacker))490 for i := 0; i < MaxSplitsPerOwner; i++ {491 id := CreateSplit(cross(cur), addrList(alice), "10000")492 TransferOwnership(cross(cur), id, bob) // staged at the victim, never accepted493 }494 // victim quota untouched; victim can still create a full quota of splits495 uassert.Equal(t, 0, ownerSplits[bob])496 testing.SetRealm(testing.NewUserRealm(bob))497 for i := 0; i < MaxSplitsPerOwner; i++ {498 CreateSplit(cross(cur), addrList(alice), "10000")499 }500 // while the attacker's own budget stays consumed — as documented501 uassert.Equal(t, MaxSplitsPerOwner, ownerSplits[attacker])502}503504// Self-transfer is refused at STAGE time. This guard is what makes505// self-accept unreachable, and so what keeps AcceptOwnership's -1/+1506// quota pair from collapsing onto one address (v2 audit G-7.1).507func TestTransferOwnership_SelfRefused(cur realm, t *testing.T) {508 resetState()509 testing.SetRealm(testing.NewUserRealm(owner))510 id := CreateSplit(cross(cur), addrList(alice), "10000")511512 uassert.AbortsWithMessage(t, cur,513 "new owner is already the owner",514 func() { TransferOwnership(cross(cur), id, owner) },515 )516 uassert.Equal(t, 1, ownerSplits[owner])517}518519// Re-staging overwrites the prior nomination; only the latest nominee520// can accept (v2 audit G-7.4).521func TestTransferOwnership_RestageOverwrites(cur realm, t *testing.T) {522 resetState()523 testing.SetRealm(testing.NewUserRealm(owner))524 id := CreateSplit(cross(cur), addrList(alice), "10000")525 TransferOwnership(cross(cur), id, bob)526 TransferOwnership(cross(cur), id, alice)527 uassert.Equal(t, string(alice), GetPendingOwner(id))528529 testing.SetRealm(testing.NewUserRealm(bob))530 uassert.AbortsWithMessage(t, cur,531 "caller is not the pending owner",532 func() { AcceptOwnership(cross(cur), id) },533 )534 testing.SetRealm(testing.NewUserRealm(alice))535 AcceptOwnership(cross(cur), id)536 uassert.Equal(t, string(alice), string(splits[id].Owner))537}538539// A nomination on a split that is later archived is inert — the doc540// comment on AcceptOwnership claims it, this pins it (v2 audit G-7.2).541func TestAcceptOwnership_ArchivedRefused(cur realm, t *testing.T) {542 resetState()543 testing.SetRealm(testing.NewUserRealm(owner))544 id := CreateSplit(cross(cur), addrList(alice), "10000")545 TransferOwnership(cross(cur), id, bob)546 Archive(cross(cur), id)547548 testing.SetRealm(testing.NewUserRealm(bob))549 uassert.AbortsWithMessage(t, cur, "split is archived: "+id,550 func() { AcceptOwnership(cross(cur), id) },551 )552 // the archived split's slot stays returned to its creator, not the nominee553 uassert.Equal(t, 0, ownerSplits[owner])554 uassert.Equal(t, 0, ownerSplits[bob])555}556557// Freezing locks shares and deposits, not custody: a frozen split stays558// transferable so its owner can hand off claim-servicing duty559// (v2 audit G-7.3).560func TestAcceptOwnership_FrozenAllowed(cur realm, t *testing.T) {561 resetState()562 testing.SetRealm(testing.NewUserRealm(owner))563 id := CreateSplit(cross(cur), addrList(alice), "10000")564 TransferOwnership(cross(cur), id, bob)565 Freeze(cross(cur), id)566567 testing.SetRealm(testing.NewUserRealm(bob))568 AcceptOwnership(cross(cur), id)569 uassert.Equal(t, string(bob), string(splits[id].Owner))570 uassert.True(t, splits[id].Frozen)571}572573// The nominee can read the offer without paying gas to probe it, and the574// pending line disappears once consumed or cleared (v2 audit Y1-obs).575func TestPendingOwner_ReadSurface(cur realm, t *testing.T) {576 resetState()577 testing.SetRealm(testing.NewUserRealm(owner))578 id := CreateSplit(cross(cur), addrList(alice), "10000")579 uassert.Equal(t, "", GetPendingOwner(id))580 uassert.False(t, strings.Contains(GetSplitInfo(id), "Pending owner:"))581582 TransferOwnership(cross(cur), id, bob)583 uassert.Equal(t, string(bob), GetPendingOwner(id))584 uassert.True(t, strings.Contains(GetSplitInfo(id), "Pending owner: "+string(bob)))585 uassert.True(t, strings.Contains(Render(""), "Pending owner: "+string(bob)))586587 testing.SetRealm(testing.NewUserRealm(bob))588 AcceptOwnership(cross(cur), id)589 uassert.Equal(t, "", GetPendingOwner(id))590 uassert.False(t, strings.Contains(GetSplitInfo(id), "Pending owner:"))591}592593// ---------- Freeze ----------594595func TestFreeze_Basic(cur realm, t *testing.T) {596 resetState()597 testing.SetRealm(testing.NewUserRealm(owner))598 id := CreateSplit(cross(cur), addrList(alice), "10000")599600 Freeze(cross(cur), id)601 uassert.True(t, splits[id].Frozen, "split must be frozen after Freeze()")602}603604func TestFreeze_WrongOwner(cur realm, t *testing.T) {605 resetState()606 testing.SetRealm(testing.NewUserRealm(owner))607 id := CreateSplit(cross(cur), addrList(alice), "10000")608609 testing.SetRealm(testing.NewUserRealm(attacker))610 uassert.AbortsWithMessage(t, cur,611 "only the owner can freeze",612 func() { Freeze(cross(cur), id) },613 )614}615616func TestFreeze_ClaimsStillWork(cur realm, t *testing.T) {617 resetState()618 testing.SetRealm(testing.NewUserRealm(owner))619 id := CreateSplit(cross(cur), addrList(alice), "10000")620 deposit(cur, owner, id, 100)621622 testing.SetRealm(testing.NewUserRealm(owner))623 Freeze(cross(cur), id)624625 testing.SetRealm(testing.NewUserRealm(alice))626 uassert.Equal(t, int64(100), Claim(cross(cur), id))627}628629// ---------- Archive ----------630631func TestArchive_BlocksWithOutstandingBalance(cur realm, t *testing.T) {632 resetState()633 testing.SetRealm(testing.NewUserRealm(owner))634 id := CreateSplit(cross(cur), addrList(alice), "10000")635 deposit(cur, owner, id, 100)636637 testing.SetRealm(testing.NewUserRealm(owner))638 uassert.AbortsWithMessage(t, cur,639 "cannot archive: outstanding balance for "+string(alice),640 func() { Archive(cross(cur), id) },641 )642}643644func TestArchive_BlocksOnExRecipientBalance(cur realm, t *testing.T) {645 resetState()646 testing.SetRealm(testing.NewUserRealm(owner))647 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")648 deposit(cur, owner, id, 1000)649650 testing.SetRealm(testing.NewUserRealm(owner))651 // remove bob; his 500 stays claimable652 UpdateShares(cross(cur), id, addrList(alice), "10000")653 testing.SetRealm(testing.NewUserRealm(alice))654 Claim(cross(cur), id)655656 // archive must refuse while the EX-recipient still has a balance —657 // archiving would lock his claim forever658 testing.SetRealm(testing.NewUserRealm(owner))659 uassert.AbortsWithMessage(t, cur,660 "cannot archive: outstanding balance for "+string(bob),661 func() { Archive(cross(cur), id) },662 )663664 // once bob claims, archive succeeds665 testing.SetRealm(testing.NewUserRealm(bob))666 Claim(cross(cur), id)667 testing.SetRealm(testing.NewUserRealm(owner))668 Archive(cross(cur), id)669 uassert.True(t, splits[id].Archived, "split must be archived")670}671672// ---------- queries ----------673674func TestGetClaimable(cur realm, t *testing.T) {675 resetState()676 testing.SetRealm(testing.NewUserRealm(owner))677 id := CreateSplit(cross(cur), addrList(alice, bob), "8000,2000")678 deposit(cur, owner, id, 5000)679680 uassert.Equal(t, int64(4000), GetClaimable(id, alice))681 uassert.Equal(t, int64(1000), GetClaimable(id, bob))682 uassert.Equal(t, int64(0), GetClaimable(id, attacker))683}684685func TestGetSplitInfo_ContainsExpectedFields(cur realm, t *testing.T) {686 resetState()687 testing.SetRealm(testing.NewUserRealm(owner))688 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")689 deposit(cur, owner, id, 200)690691 info := GetSplitInfo(id)692 uassert.True(t, strings.Contains(info, "Owner"), "GetSplitInfo output must contain 'Owner'")693 uassert.True(t, strings.Contains(info, string(owner)), "GetSplitInfo output must contain owner address")694 uassert.True(t, strings.Contains(info, string(alice)), "GetSplitInfo output must contain recipient")695}696697// ---------- render ----------698699func TestRender_EmptyState(cur realm, t *testing.T) {700 resetState()701702 out := Render("")703 uassert.Equal(t, "# Fee Split\n\nNo active splits.\n", out)704}705706func TestRender_WithSplits(cur realm, t *testing.T) {707 resetState()708 testing.SetRealm(testing.NewUserRealm(owner))709 CreateSplit(cross(cur), addrList(alice, bob), "6000,4000")710 deposit(cur, owner, "split_1", 1000)711712 out := Render("")713 uassert.True(t, strings.Contains(out, "split_1"), "Render output must reference split_1")714 uassert.True(t, strings.Contains(out, string(owner)), "Render output must include owner")715}716717// ---------- re-audit regressions (2026-09-02) ----------718719func TestCreateSplit_ShareSumWraparoundRejected(cur realm, t *testing.T) {720 resetState()721 testing.SetRealm(testing.NewUserRealm(owner))722723 // re-audit P1: these shares sum past 2^64 back to exactly 10000; the724 // per-share <=10000 bound must refuse them before the sum can wrap725 uassert.AbortsWithMessage(t, cur,726 "invalid share value: 1152921505606846976",727 func() {728 CreateSplit(cross(cur), addrList(alice, bob, carol),729 "1152921505606846976,8646911284051357320,8646911284051357320")730 },731 )732 uassert.AbortsWithMessage(t, cur,733 "invalid share value: 10001",734 func() { CreateSplit(cross(cur), addrList(alice, bob), "10001,10000") },735 )736}737738func TestArchiveFreesActiveListAndOwnerSlot(cur realm, t *testing.T) {739 resetState()740 testing.SetRealm(testing.NewUserRealm(owner))741742 id := CreateSplit(cross(cur), addrList(alice), "10000")743 Archive(cross(cur), id)744 uassert.Equal(t, 1, len(splits), "archived entry stays for audit")745 uassert.Equal(t, 0, len(splitIDs), "archived entry frees the active list")746 CreateSplit(cross(cur), addrList(alice), "10000") // must not abort747}748749func TestNoGlobalCap_SybilsCannotBlockOthers(cur realm, t *testing.T) {750 resetState()751752 // round-4 P2: with per-owner quotas only, other owners filling their753 // own budgets never block a legitimate creator754 for i := 0; i < 30; i++ {755 syb := testutils.TestAddress("sybil_" + strconv.Itoa(i))756 testing.SetRealm(testing.NewUserRealm(syb))757 for j := 0; j < MaxSplitsPerOwner; j++ {758 CreateSplit(cross(cur), addrList(alice), "10000")759 }760 }761 uassert.Equal(t, 300, len(splitIDs))762763 testing.SetRealm(testing.NewUserRealm(owner))764 CreateSplit(cross(cur), addrList(alice), "10000") // must not abort765}766767func TestPreParseInputBounds(cur realm, t *testing.T) {768 resetState()769 testing.SetRealm(testing.NewUserRealm(owner))770771 // round-4 P2: oversized inputs must be refused BEFORE parsing work772 uassert.AbortsWithMessage(t, cur,773 "recipient list too long",774 func() { CreateSplit(cross(cur), strings.Repeat(",", MaxRecipientListLen+1), "10000") },775 )776 uassert.AbortsWithMessage(t, cur,777 "share list too long",778 func() { CreateSplit(cross(cur), addrList(alice), strings.Repeat("1,", MaxShareListLen)) },779 )780}781782func TestStraySendRejected(cur realm, t *testing.T) {783 resetState()784 testing.SetRealm(testing.NewUserRealm(owner))785 id := CreateSplit(cross(cur), addrList(alice), "10000")786 deposit(cur, owner, id, 100)787788 // re-audit P3: coins attached to a non-Deposit call must abort (and789 // therefore revert to the sender), never strand on the realm address790 testing.SetRealm(testing.NewUserRealm(alice))791 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: 50}})792 uassert.AbortsWithMessage(t, cur,793 "this function does not accept coins; attach coins to Deposit only",794 func() { Claim(cross(cur), id) },795 )796 testing.SetOriginSend(nil)797798 // without coins attached the same claim succeeds799 uassert.Equal(t, int64(100), Claim(cross(cur), id))800}801802func TestDeposit_CodeRealmRefused(cur realm, t *testing.T) {803 resetState()804 testing.SetRealm(testing.NewUserRealm(owner))805 id := CreateSplit(cross(cur), addrList(alice), "10000")806807 // re-audit P1 companion: only a direct user MsgCall may deposit — a808 // code realm as previous realm must be refused (its send would not809 // have landed here). The MsgRun/ephemeral shape (the other half of810 // the IsUserCall-not-IsUser rationale) cannot be constructed in this811 // driver: SetRealm accepts only user realms and /r/ code realms812 // (NewCodeRealm rejects /e/ paths), so the predicate choice is813 // pinned here via the code-realm branch and verified live on-chain.814 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/intermediary"))815 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: 100}})816 uassert.AbortsContains(t, cur,817 "deposits must be sent by direct call",818 func() { Deposit(cross(cur), id) },819 )820 testing.SetOriginSend(nil)821}822823// ---------- round-3 audit regressions (2026-09-02) ----------824825func TestUppercaseAddressesRejected(cur realm, t *testing.T) {826 resetState()827 testing.SetRealm(testing.NewUserRealm(owner))828829 // round-3 P2: all-uppercase bech32 passes IsValid but can never830 // match caller(): as a recipient it locks funds and blocks Archive;831 // as upper/lower twins it bypasses the duplicate check; as an owner832 // it orphans the split833 upper := strings.ToUpper(string(alice))834 uassert.AbortsWithMessage(t, cur,835 "invalid recipient address: "+upper,836 func() { CreateSplit(cross(cur), string(alice)+","+upper, "5000,5000") },837 )838839 id := CreateSplit(cross(cur), addrList(alice), "10000")840 uassert.AbortsWithMessage(t, cur,841 "invalid new owner address: "+upper,842 func() { TransferOwnership(cross(cur), id, address(upper)) },843 )844}845846func TestRender_OnlyArchivedSplits(cur realm, t *testing.T) {847 resetState()848 testing.SetRealm(testing.NewUserRealm(owner))849 id := CreateSplit(cross(cur), addrList(alice), "10000")850 Archive(cross(cur), id)851852 uassert.Equal(t, "# Fee Split\n\nNo active splits.\n", Render(""),853 "archived-only state must render as no active splits")854}855856// ---------- protocol fee (2026-09-03) ----------857858func TestFee_DefaultZeroAndCap(cur realm, t *testing.T) {859 resetState()860 uassert.Equal(t, int64(0), feeBps)861862 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))863 uassert.AbortsContains(t, cur, "fee must be between 0 and",864 func() { SetFee(cross(cur), MaxFeeBps+1) })865 uassert.AbortsContains(t, cur, "fee must be between 0 and",866 func() { SetFee(cross(cur), -1) })867 SetFee(cross(cur), MaxFeeBps) // exactly the cap is allowed868 uassert.Equal(t, MaxFeeBps, feeBps)869870 testing.SetRealm(testing.NewUserRealm(attacker))871 uassert.AbortsWithMessage(t, cur, "only the fee admin can set the fee",872 func() { SetFee(cross(cur), 1) })873}874875func TestFee_TakenFromDepositNetAccounting(cur realm, t *testing.T) {876 resetState()877 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))878 SetFee(cross(cur), 50) // 0.5%879880 testing.SetRealm(testing.NewUserRealm(owner))881 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")882 deposit(cur, owner, id, 10000)883884 s := splits[id]885 // fee 50 off the top; 9950 net distributed (4975 + 4975)886 uassert.Equal(t, int64(50), feesAccrued)887 uassert.Equal(t, int64(9950), s.TotalDeposited)888 uassert.Equal(t, int64(4975), s.Balances[alice])889 uassert.Equal(t, int64(4975), s.Balances[bob])890 // conservation on net amounts holds untouched891 uassert.Equal(t, s.TotalDeposited, s.Balances[alice]+s.Balances[bob]+s.TotalClaimed)892}893894func TestFee_NeverRetroactive(cur realm, t *testing.T) {895 resetState()896 testing.SetRealm(testing.NewUserRealm(owner))897 id := CreateSplit(cross(cur), addrList(alice), "10000")898 deposit(cur, owner, id, 10000) // fee 0 at deposit time899900 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))901 SetFee(cross(cur), MaxFeeBps)902903 // the previously credited balance is untouched by the fee change904 uassert.Equal(t, int64(10000), splits[id].Balances[alice])905 testing.SetRealm(testing.NewUserRealm(alice))906 uassert.Equal(t, int64(10000), Claim(cross(cur), id))907}908909func TestFee_ClaimFeesSendsCoins(cur realm, t *testing.T) {910 resetState()911 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))912 SetFee(cross(cur), 100) // 1%913914 testing.SetRealm(testing.NewUserRealm(owner))915 id := CreateSplit(cross(cur), addrList(alice), "10000")916 deposit(cur, owner, id, 50000) // fee 500917918 testing.SetRealm(testing.NewUserRealm(attacker))919 uassert.AbortsWithMessage(t, cur, "only the fee admin can claim fees",920 func() { ClaimFees(cross(cur)) })921922 before := banker.NewReadonlyBanker().GetCoins(feeAdminAddr).AmountOf(Denom)923 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))924 uassert.Equal(t, int64(500), ClaimFees(cross(cur)))925 after := banker.NewReadonlyBanker().GetCoins(feeAdminAddr).AmountOf(Denom)926 uassert.Equal(t, int64(500), after-before)927 uassert.Equal(t, int64(0), feesAccrued)928 uassert.Equal(t, int64(500), feesClaimed)929930 uassert.AbortsWithMessage(t, cur, "no fees accrued", func() { ClaimFees(cross(cur)) })931}932933func TestFee_TinyDepositFullyConsumedRefused(cur realm, t *testing.T) {934 resetState()935 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))936 SetFee(cross(cur), MaxFeeBps) // 1%937938 testing.SetRealm(testing.NewUserRealm(owner))939 id := CreateSplit(cross(cur), addrList(alice), "10000")940 // amounts < 100 at 1% round the fee to 0 — deposit passes whole941 deposit(cur, owner, id, 99)942 uassert.Equal(t, int64(0), feesAccrued)943 uassert.Equal(t, int64(99), splits[id].Balances[alice])944}945946func TestFee_TwoStepAdminHandover(cur realm, t *testing.T) {947 resetState()948 newAdmin := testutils.TestAddress("newfeeadmin")949950 testing.SetRealm(testing.NewUserRealm(attacker))951 uassert.AbortsWithMessage(t, cur, "only the fee admin can nominate a successor",952 func() { NominateFeeAdmin(cross(cur), attacker) })953954 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))955 uassert.AbortsContains(t, cur, "invalid nominee address",956 func() { NominateFeeAdmin(cross(cur), address("JUNK")) })957 NominateFeeAdmin(cross(cur), newAdmin)958 // nomination alone changes nothing959 uassert.Equal(t, string(feeAdminAddr), string(feeAdmin))960961 testing.SetRealm(testing.NewUserRealm(attacker))962 uassert.AbortsWithMessage(t, cur, "caller is not the pending fee admin",963 func() { AcceptFeeAdmin(cross(cur)) })964965 testing.SetRealm(testing.NewUserRealm(newAdmin))966 AcceptFeeAdmin(cross(cur))967 uassert.Equal(t, string(newAdmin), string(feeAdmin))968 uassert.Equal(t, "", string(pendingFeeAdmin))969970 // clearing a nomination works971 testing.SetRealm(testing.NewUserRealm(newAdmin))972 NominateFeeAdmin(cross(cur), feeAdminAddr)973 NominateFeeAdmin(cross(cur), "")974 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))975 uassert.AbortsWithMessage(t, cur, "caller is not the pending fee admin",976 func() { AcceptFeeAdmin(cross(cur)) })977}978979func TestFee_StraySendRejectedOnFeeFunctions(cur realm, t *testing.T) {980 resetState()981 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))982 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: 5}})983 uassert.AbortsWithMessage(t, cur,984 "this function does not accept coins; attach coins to Deposit only",985 func() { SetFee(cross(cur), 10) },986 )987 testing.SetOriginSend(nil)988}989990// ---------- round-5 audit regressions (fee path, 2026-09-03) ----------991992// Pins the benign-degradation safety property: an empty fee admin (the993// gno-test-VM init state, and any hypothetical empty-origin deploy)994// leaves the fee permanently disabled and zero funds at risk — SetFee995// can never be authorized, so feeBps stays 0 and nothing accrues.996func TestFee_EmptyAdminDegradesToDisabled(cur realm, t *testing.T) {997 resetState()998 feeAdmin = ""999 feeBps = 010001001 for _, who := range []address{owner, alice, feeAdminAddr, attacker} {1002 testing.SetRealm(testing.NewUserRealm(who))1003 uassert.AbortsWithMessage(t, cur, "only the fee admin can set the fee",1004 func() { SetFee(cross(cur), 100) })1005 }1006 uassert.Equal(t, int64(0), feeBps, "fee must remain 0 with no reachable admin")10071008 // a deposit accrues nothing1009 testing.SetRealm(testing.NewUserRealm(owner))1010 id := CreateSplit(cross(cur), addrList(alice), "10000")1011 deposit(cur, owner, id, 10000)1012 uassert.Equal(t, int64(0), feesAccrued)1013 testing.SetRealm(testing.NewUserRealm(alice))1014 uassert.Equal(t, int64(10000), Claim(cross(cur), id)) // full amount, no fee1015}10161017// Fees accrued before a handover are claimable by the NEW admin1018// (documented semantics: claim before transferring if that matters).1019func TestFee_HandoverTransfersAccruedClaimRight(cur realm, t *testing.T) {1020 resetState()1021 newFa := testutils.TestAddress("newfa")1022 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))1023 SetFee(cross(cur), 100)1024 testing.SetRealm(testing.NewUserRealm(owner))1025 id := CreateSplit(cross(cur), addrList(alice), "10000")1026 deposit(cur, owner, id, 100000) // 1000 accrued under the original admin10271028 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))1029 NominateFeeAdmin(cross(cur), newFa)1030 testing.SetRealm(testing.NewUserRealm(newFa))1031 AcceptFeeAdmin(cross(cur))1032 uassert.Equal(t, int64(1000), ClaimFees(cross(cur)), "new admin claims prior-regime fees")10331034 // the old admin can no longer claim — fails the authorization check1035 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))1036 uassert.AbortsWithMessage(t, cur, "only the fee admin can claim fees", func() { ClaimFees(cross(cur)) })1037}10381039// Every fee function rejects an attached send (only SetFee was pinned).1040func TestFee_AllFunctionsRejectStraySend(cur realm, t *testing.T) {1041 resetState()1042 testing.SetRealm(testing.NewUserRealm(feeAdminAddr))1043 send := chain.Coins{{Denom: Denom, Amount: 3}}1044 msg := "this function does not accept coins; attach coins to Deposit only"10451046 testing.SetOriginSend(send)1047 uassert.AbortsWithMessage(t, cur, msg, func() { SetFee(cross(cur), 10) })1048 uassert.AbortsWithMessage(t, cur, msg, func() { NominateFeeAdmin(cross(cur), attacker) })1049 uassert.AbortsWithMessage(t, cur, msg, func() { ClaimFees(cross(cur)) })1050 testing.SetOriginSend(nil)10511052 // AcceptFeeAdmin from the pending side, also with a send1053 NominateFeeAdmin(cross(cur), attacker)1054 testing.SetRealm(testing.NewUserRealm(attacker))1055 testing.SetOriginSend(send)1056 uassert.AbortsWithMessage(t, cur, msg, func() { AcceptFeeAdmin(cross(cur)) })1057 testing.SetOriginSend(nil)1058}10591060// ---------- audit round-1 gap closures (2026-09-21) ----------10611062func TestStraySendRejectedOnAllSplitFunctions(cur realm, t *testing.T) {1063 resetState()1064 testing.SetRealm(testing.NewUserRealm(owner))1065 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")10661067 // audit Y2 gap 2: only Claim and the fee functions were pinned; the1068 // five split-management functions call the same guard — prove it1069 msg := "this function does not accept coins; attach coins to Deposit only"1070 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: 7}})1071 uassert.AbortsWithMessage(t, cur, msg, func() { CreateSplit(cross(cur), addrList(carol), "10000") })1072 uassert.AbortsWithMessage(t, cur, msg, func() { UpdateShares(cross(cur), id, addrList(alice), "10000") })1073 uassert.AbortsWithMessage(t, cur, msg, func() { TransferOwnership(cross(cur), id, carol) })1074 uassert.AbortsWithMessage(t, cur, msg, func() { AcceptOwnership(cross(cur), id) })1075 uassert.AbortsWithMessage(t, cur, msg, func() { Freeze(cross(cur), id) })1076 uassert.AbortsWithMessage(t, cur, msg, func() { Archive(cross(cur), id) })1077 testing.SetOriginSend(nil)1078}10791080func TestArchivedSplitRefusesDepositAndClaim(cur realm, t *testing.T) {1081 resetState()1082 testing.SetRealm(testing.NewUserRealm(owner))1083 id := CreateSplit(cross(cur), addrList(alice), "10000")1084 deposit(cur, owner, id, 100)1085 testing.SetRealm(testing.NewUserRealm(alice))1086 Claim(cross(cur), id)1087 testing.SetRealm(testing.NewUserRealm(owner))1088 Archive(cross(cur), id)10891090 // audit Y2 gap 3: the terminal state must refuse both value paths1091 testing.SetRealm(testing.NewUserRealm(owner))1092 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: 50}})1093 uassert.AbortsWithMessage(t, cur, "split is archived: "+id,1094 func() { Deposit(cross(cur), id) })1095 testing.SetOriginSend(nil)1096 testing.SetRealm(testing.NewUserRealm(alice))1097 uassert.AbortsWithMessage(t, cur, "split is archived: "+id,1098 func() { Claim(cross(cur), id) })1099}11001101func TestDeposit_TwoCoinEnvelopeRejected(cur realm, t *testing.T) {1102 resetState()1103 testing.SetRealm(testing.NewUserRealm(owner))1104 id := CreateSplit(cross(cur), addrList(alice), "10000")11051106 // audit Y2 gap 4: only zero-coin and wrong-denom were pinned; a1107 // two-coin envelope must also be refused by the exact len==1 check1108 testing.SetOriginSend(chain.Coins{{Denom: Denom, Amount: 100}, {Denom: "ufoo", Amount: 1}})1109 uassert.AbortsWithMessage(t, cur,1110 "deposit must send exactly one coin of "+Denom,1111 func() { Deposit(cross(cur), id) })1112 testing.SetOriginSend(nil)1113 uassert.Equal(t, int64(0), splits[id].TotalDeposited)1114}11151116func TestDust_EqualShareTieGoesToFirst(cur realm, t *testing.T) {1117 resetState()1118 testing.SetRealm(testing.NewUserRealm(owner))1119 id := CreateSplit(cross(cur), addrList(alice, bob), "5000,5000")11201121 // audit Y2 gap 5: the dust rule at an exact share tie is1122 // first-listed-wins (strict > never replaces dustIdx) — pin it1123 deposit(cur, owner, id, 101)1124 uassert.Equal(t, int64(51), splits[id].Balances[alice])1125 uassert.Equal(t, int64(50), splits[id].Balances[bob])1126}1127AcceptFeeAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
AcceptOwnership(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, splitID string)
Archive(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, splitID string)
Claim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, splitID string) int64
ClaimFees(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}) int64
CreateSplit(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, recipientList string, shareList string) string
Deposit(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, splitID string)
Freeze(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, splitID string)
GetClaimable(splitID string, addr string) int64
GetFeeInfo() string
GetPendingOwner(splitID string) string
GetSplitInfo(splitID string) string
NominateFeeAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, nominee string)
Render(path string) string
SetFee(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, bps int64)
TransferOwnership(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, splitID string, newOwner string)
UpdateShares(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, splitID string, recipientList string, shareList string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.