PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

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/nt/grc20/v0

Package
Open in gnoweb ↗

Overview

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

Files (12)

  • gnomod.tomltoml
  • mock.gnogno
  • tellers.gnogno
  • token.gnogno
  • types.gnogno
  • caller_teller_sub_realm_filetest.gnogno
  • event_provenance_filetest.gno
gno
  • examples_test.gnogno
  • newtoken_event_filetest.gnogno
  • tellers_test.gnogno
  • token_identity_filetest.gnogno
  • token_test.gnogno
  • token_test.gnogno
    1package grc2023import (4	"errors"5	"math"6	"strings"7	"testing"8

    Functions

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

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

    9 "gno.land/p/nt/seqid/v0"
    10 "gno.land/p/nt/testutils/v0"
    11 "gno.land/p/nt/uassert/v0"
    12 "gno.land/p/nt/ufmt/v0"
    13 "gno.land/p/nt/urequire/v0"
    14)
    15
    16// newTestToken constructs a Token. The IIFE same-realm cross
    17// promotes the test's EOA-origin cur into a fresh /p/grc20
    18// CodeRealm cur so NewToken's IsCurrent + non-empty rlm.PkgPath()
    19// checks pass.
    20func newTestToken(name, symbol string, decimals int, id seqid.ID, rlm realm) (tok *Token, adm *PrivateLedger) {
    21 func(cur realm) {
    22 tok, adm = NewToken(name, symbol, decimals, id, cur)
    23 }(cross(rlm))
    24 return
    25}
    26
    27func TestTestImpl(cur realm, t *testing.T) {
    28 bank, _ := newTestToken("Dummy", "DUMMY", 4, 0, cur)
    29 urequire.False(t, bank == nil, "dummy should not be nil")
    30}
    31
    32func TestNewTokenAllowsDuplicateSymbolInSameRealm(cur realm, t *testing.T) {
    33 holder := testutils.TestAddress("holder")
    34
    35 first, firstLedger := newTestToken("Same", "DUP", 6, 1, cur)
    36 second, secondLedger := newTestToken("Same", "DUP", 6, 2, cur)
    37
    38 urequire.True(t, first.ID() != second.ID(), "duplicate symbols should not force duplicate IDs")
    39
    40 urequire.NoError(t, firstLedger.Mint(holder, 11))
    41 urequire.NoError(t, secondLedger.Mint(holder, 22))
    42 urequire.Equal(t, int64(11), first.BalanceOf(holder))
    43 urequire.Equal(t, int64(22), second.BalanceOf(holder))
    44}
    45
    46func TestNewTokenValidation(cur realm, t *testing.T) {
    47 // Wrap NewToken to satisfy IsCurrent and pkgPath requirements,
    48 // then validate name, symbol, and decimals.
    49 mustPanic := func(name, sym string, dec int, want error, label string) {
    50 t.Helper()
    51 // newTestToken wraps NewToken in cross(...), so the panic from
    52 // NewToken's validators crosses a realm boundary — use revive()
    53 // (defer-recover doesn't see cross-realm panics).
    54 r := revive(func() {
    55 newTestToken(name, sym, dec, 0, cur)
    56 })
    57 if r == nil {
    58 t.Errorf("%s: expected panic, got none", label)
    59 return
    60 }
    61 if r != want {
    62 t.Errorf("%s: expected %v, got %v", label, want, r)
    63 }
    64 }
    65
    66 // Empty name / symbol.
    67 mustPanic("", "OK", 4, ErrInvalidName, "empty name")
    68 mustPanic("Name", "", 4, ErrInvalidSymbol, "empty symbol")
    69
    70 // Length caps.
    71 mustPanic(strings.Repeat("a", MaxNameLen+1), "OK", 4, ErrInvalidName, "name too long")
    72 mustPanic("Name", strings.Repeat("A", MaxSymbolLen+1), 4, ErrInvalidSymbol, "symbol too long")
    73
    74 // Name control characters.
    75 mustPanic("bad\x01name", "OK", 4, ErrInvalidName, "name with control char")
    76 mustPanic("bad\nname", "OK", 4, ErrInvalidName, "name with newline")
    77
    78 // Symbol charset — disallowed delimiters and whitespace.
    79 mustPanic("Name", "BA.D", 4, ErrInvalidSymbol, "symbol with dot")
    80 mustPanic("Name", "BA/D", 4, ErrInvalidSymbol, "symbol with slash")
    81 mustPanic("Name", "BA D", 4, ErrInvalidSymbol, "symbol with space")
    82 mustPanic("Name", "BA\"D", 4, ErrInvalidSymbol, "symbol with quote")
    83
    84 // Decimals out of range.
    85 mustPanic("Name", "OK", -1, ErrInvalidDecimals, "negative decimals")
    86 mustPanic("Name", "OK", MaxDecimals+1, ErrInvalidDecimals, "decimals over cap")
    87
    88 // Boundary positives — should NOT panic.
    89 tok, _ := newTestToken(strings.Repeat("a", MaxNameLen), strings.Repeat("A", MaxSymbolLen), MaxDecimals, 0, cur)
    90 urequire.True(t, tok != nil, "boundary name+symbol+decimals should succeed")
    91
    92 // UTF-8 name with non-ASCII is allowed.
    93 tok2, _ := newTestToken("Доллар", "RUB", 2, 0, cur)
    94 urequire.True(t, tok2 != nil, "UTF-8 name should be allowed")
    95}
    96
    97func TestToken(cur realm, t *testing.T) {
    98 var (
    99 alice = testutils.TestAddress("alice")
    100 bob = testutils.TestAddress("bob")
    101 carl = testutils.TestAddress("carl")
    102 )
    103
    104 bank, adm := newTestToken("Dummy", "DUMMY", 6, 0, cur)
    105
    106 checkBalances := func(aliceEB, bobEB, carlEB int64) {
    107 t.Helper()
    108 exp := ufmt.Sprintf("alice=%d bob=%d carl=%d", aliceEB, bobEB, carlEB)
    109 aliceGB := bank.BalanceOf(alice)
    110 bobGB := bank.BalanceOf(bob)
    111 carlGB := bank.BalanceOf(carl)
    112 got := ufmt.Sprintf("alice=%d bob=%d carl=%d", aliceGB, bobGB, carlGB)
    113 uassert.Equal(t, got, exp, "invalid balances")
    114 }
    115 checkAllowances := func(abEB, acEB, baEB, bcEB, caEB, cbEB int64) {
    116 t.Helper()
    117 exp := ufmt.Sprintf("ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s", abEB, acEB, baEB, bcEB, caEB, cbEB)
    118 abGB := bank.Allowance(alice, bob)
    119 acGB := bank.Allowance(alice, carl)
    120 baGB := bank.Allowance(bob, alice)
    121 bcGB := bank.Allowance(bob, carl)
    122 caGB := bank.Allowance(carl, alice)
    123 cbGB := bank.Allowance(carl, bob)
    124 got := ufmt.Sprintf("ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s", abGB, acGB, baGB, bcGB, caGB, cbGB)
    125 uassert.Equal(t, got, exp, "invalid allowances")
    126 }
    127
    128 checkBalances(0, 0, 0)
    129 checkAllowances(0, 0, 0, 0, 0, 0)
    130
    131 urequire.NoError(t, adm.Mint(alice, 1000))
    132 urequire.NoError(t, adm.Mint(alice, 100))
    133 checkBalances(1100, 0, 0)
    134 checkAllowances(0, 0, 0, 0, 0, 0)
    135
    136 urequire.NoError(t, adm.Approve(alice, bob, 99999999))
    137 checkBalances(1100, 0, 0)
    138 checkAllowances(99999999, 0, 0, 0, 0, 0)
    139
    140 urequire.NoError(t, adm.Approve(alice, bob, 400))
    141 checkBalances(1100, 0, 0)
    142 checkAllowances(400, 0, 0, 0, 0, 0)
    143
    144 urequire.Error(t, adm.TransferFrom(alice, bob, carl, 100000000))
    145 checkBalances(1100, 0, 0)
    146 checkAllowances(400, 0, 0, 0, 0, 0)
    147
    148 urequire.NoError(t, adm.TransferFrom(alice, bob, carl, 100))
    149 checkBalances(1000, 0, 100)
    150 checkAllowances(300, 0, 0, 0, 0, 0)
    151
    152 urequire.Error(t, adm.SpendAllowance(alice, bob, 2000000))
    153 checkBalances(1000, 0, 100)
    154 checkAllowances(300, 0, 0, 0, 0, 0)
    155
    156 urequire.NoError(t, adm.SpendAllowance(alice, bob, 100))
    157 checkBalances(1000, 0, 100)
    158 checkAllowances(200, 0, 0, 0, 0, 0)
    159}
    160
    161func TestMintOverflow(cur realm, t *testing.T) {
    162 alice := testutils.TestAddress("alice")
    163 bob := testutils.TestAddress("bob")
    164 tok, adm := newTestToken("Dummy", "DUMMY", 6, 0, cur)
    165
    166 safeValue := int64(1 << 62)
    167 urequire.NoError(t, adm.Mint(alice, safeValue))
    168 urequire.Equal(t, tok.BalanceOf(alice), safeValue)
    169
    170 err := adm.Mint(bob, safeValue)
    171 uassert.Error(t, err, "expected ErrMintOverflow")
    172}
    173
    174func TestTransferFromAtomicity(cur realm, t *testing.T) {
    175 var (
    176 owner = testutils.TestAddress("owner")
    177 spender = testutils.TestAddress("spender")
    178
    179 invalidRecipient = address("")
    180 recipient = testutils.TestAddress("to")
    181 )
    182
    183 token, admin := newTestToken("Test", "TEST", 6, 0, cur)
    184
    185 // owner has 100 tokens, spender has 50 allowance
    186 initialBalance := int64(100)
    187 initialAllowance := int64(50)
    188
    189 urequire.NoError(t, admin.Mint(owner, initialBalance))
    190 urequire.NoError(t, admin.Approve(owner, spender, initialAllowance))
    191
    192 // transfer to an invalid address to force a transfer failure
    193 transferAmount := int64(30)
    194 err := admin.TransferFrom(owner, spender, invalidRecipient, transferAmount)
    195 uassert.Error(t, err, "transfer should fail due to invalid address")
    196
    197 ownerBalance := token.BalanceOf(owner)
    198 uassert.Equal(t, ownerBalance, initialBalance, "owner balance should remain unchanged")
    199
    200 // check if allowance was incorrectly reduced
    201 remainingAllowance := token.Allowance(owner, spender)
    202 uassert.Equal(t, remainingAllowance, initialAllowance,
    203 "allowance should not be reduced when transfer fails")
    204
    205 // Self-transfers fail in Transfer after SpendAllowance. TransferFrom must
    206 // reject them before it can mutate the allowance.
    207 err = admin.TransferFrom(owner, spender, owner, transferAmount)
    208 uassert.True(t, errors.Is(err, ErrCannotTransferToSelf), "self-transfer should fail")
    209 uassert.Equal(t, initialBalance, token.BalanceOf(owner),
    210 "owner balance should not change when self-transfer fails")
    211 uassert.Equal(t, initialAllowance, token.Allowance(owner, spender),
    212 "allowance should not be reduced when self-transfer fails")
    213
    214 // transfer all tokens
    215 admin.Transfer(owner, recipient, 100)
    216 remainingBalance := token.BalanceOf(owner)
    217 uassert.Equal(t, remainingBalance, int64(0),
    218 "balance should be zero")
    219
    220 err = admin.TransferFrom(owner, spender, recipient, transferAmount)
    221 uassert.Error(t, err, "transfer should fail due to insufficient balance")
    222}
    223
    224func TestMintUntilOverflow(cur realm, t *testing.T) {
    225 alice := testutils.TestAddress("alice")
    226 bob := testutils.TestAddress("bob")
    227 tok, adm := newTestToken("Dummy", "DUMMY", 6, 0, cur)
    228
    229 tests := []struct {
    230 name string
    231 addr address
    232 amount int64
    233 expectedError error
    234 expectedSupply int64
    235 description string
    236 }{
    237 {
    238 name: "mint negative value",
    239 addr: alice,
    240 amount: -1,
    241 expectedError: ErrInvalidAmount,
    242 expectedSupply: 0,
    243 description: "minting a negative number should fail with ErrInvalidAmount",
    244 },
    245 {
    246 name: "mint MaxInt64",
    247 addr: alice,
    248 amount: math.MaxInt64 - 1000,
    249 expectedError: nil,
    250 expectedSupply: math.MaxInt64 - 1000,
    251 description: "minting almost MaxInt64 should succeed",
    252 },
    253 {
    254 name: "mint small value",
    255 addr: bob,
    256 amount: 1000,
    257 expectedError: nil,
    258 expectedSupply: math.MaxInt64,
    259 description: "minting a small value when close to MaxInt64 should succeed",
    260 },
    261 {
    262 name: "mint value that would exceed MaxInt64",
    263 addr: bob,
    264 amount: 1,
    265 expectedError: ErrMintOverflow,
    266 expectedSupply: math.MaxInt64,
    267 description: "minting any value when at MaxInt64 should fail with ErrMintOverflow",
    268 },
    269 }
    270
    271 for _, tt := range tests {
    272 t.Run(tt.name, func(t *testing.T) {
    273 err := adm.Mint(tt.addr, tt.amount)
    274
    275 if tt.expectedError != nil {
    276 uassert.Error(t, err, tt.description)
    277 if !errors.Is(err, tt.expectedError) {
    278 t.Errorf("expected error %v, got %v", tt.expectedError, err)
    279 }
    280 } else {
    281 uassert.NoError(t, err, tt.description)
    282 }
    283
    284 totalSupply := tok.TotalSupply()
    285 uassert.Equal(t, totalSupply, tt.expectedSupply, "totalSupply should match expected value")
    286 })
    287 }
    288}
    289