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.gnogno
    1package grc2023import (4	"chain"5	"math"6	"math/overflow"7	"strconv"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/ufmt/v0"
    11)
    12
    13// NewToken creates a Token whose origRealm is bound to the calling realm.
    14// rlm must be the caller's own captured cur (asserted via rlm.IsCurrent()),
    15// and rlm.PkgPath() — the calling realm itself — becomes the Token's
    16// origRealm. Token.ID() returns origRealm + "." + symbol + "." + id.
    17//
    18// Because IsCurrent runtime-validates that rlm came from the live
    19// crossing frame, origRealm is unforgeable: an external realm cannot
    20// fabricate a Token claiming to belong to a different package.
    21//
    22// Realms that create multiple tokens should allocate id from one persistent
    23// seqid.ID, shared by every creation path, to avoid conflicting identifiers:
    24//
    25// var nextTokenID seqid.ID
    26// Token, ledger := grc20.NewToken("Foo", "FOO", 4, nextTokenID.Next(), cur)
    27//
    28// A realm that creates only a single token can pass 0 directly, since no
    29// other token of that realm can collide with it.
    30//
    31// If the Token should be discoverable, follow up with
    32// grc20reg.Register(cross(cur), Token, slug). The registry key is Token.ID().
    33//
    34// Every successful call emits a NewToken event carrying the resulting
    35// Token.ID(). Because Token's fields are unexported, NewToken is the only way a
    36// Token can come into existence, so this event makes token creation fully
    37// observable: an indexer that sees the same Token.ID() announced twice knows the
    38// realm built two independent ledgers behind one identifier, and that every
    39// later Mint/Burn/Transfer/Approval carrying that id is ambiguous. Such a realm
    40// is emitting untrustworthy events and should be flagged or ignored wholesale.
    41func NewToken(name, symbol string, decimals int, id seqid.ID, rlm realm) (*Token, *PrivateLedger) {
    42 if !rlm.IsCurrent() {
    43 panic(ErrSpoofedRealm)
    44 }
    45 pkgPath := rlm.PkgPath()
    46 if pkgPath == "" {
    47 panic(ErrNotRealm)
    48 }
    49 if !validName(name) {
    50 panic(ErrInvalidName)
    51 }
    52 if !validSymbol(symbol) {
    53 panic(ErrInvalidSymbol)
    54 }
    55 if decimals < 0 || decimals > MaxDecimals {
    56 panic(ErrInvalidDecimals)
    57 }
    58 // origRealm drops any realm.Sub subpath: a token created while its realm
    59 // operates under a sub identity still belongs to the host realm. guardHome
    60 // resolves the invoking host the same way, so storing the raw path here
    61 // would pin the token to that sub and lock its own realm out for good.
    62 origRealm, _, _ := chain.SplitPkgSubPath(pkgPath)
    63 ledger := &PrivateLedger{}
    64 token := &Token{
    65 id: pkgPath + "." + symbol + "." + id.String(),
    66 name: name,
    67 symbol: symbol,
    68 decimals: decimals,
    69 ledger: ledger,
    70 origRealm: origRealm,
    71 }
    72 ledger.token = token
    73
    74 chain.Emit(
    75 NewTokenEvent,
    76 "token", token.id,
    77 "name", name,
    78 "symbol", symbol,
    79 "decimals", strconv.Itoa(decimals),
    80 )
    81
    82 return token, ledger
    83}
    84
    85// validName reports whether name is a valid display name: non-empty,
    86// within MaxNameLen, and contains no control characters (any rune
    87// below 0x20 or 0x7f). Permits Unicode letters, digits, punctuation,
    88// and spaces — name is purely a display field.
    89func validName(name string) bool {
    90 if name == "" || len(name) > MaxNameLen {
    91 return false
    92 }
    93 for _, c := range name {
    94 if c < 0x20 || c == 0x7f {
    95 return false
    96 }
    97 }
    98 return true
    99}
    100
    101// validSymbol reports whether s is valid slug-compatible metadata: non-empty,
    102// within MaxSymbolLen, and consists only of [A-Za-z0-9_-].
    103func validSymbol(s string) bool {
    104 if s == "" || len(s) > MaxSymbolLen {
    105 return false
    106 }
    107 for _, c := range s {
    108 if !isAlnum(c) && c != '_' && c != '-' {
    109 return false
    110 }
    111 }
    112 return true
    113}
    114
    115func isAlnum(c rune) bool {
    116 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
    117}
    118
    119// GetName returns the name of the token.
    120func (tok Token) GetName() string { return tok.name }
    121
    122// GetSymbol returns the symbol of the token.
    123func (tok Token) GetSymbol() string { return tok.symbol }
    124
    125// GetDecimals returns the number of decimals used to get the token's precision.
    126func (tok Token) GetDecimals() int { return tok.decimals }
    127
    128// TotalSupply returns the total supply of the token.
    129func (tok Token) TotalSupply() int64 { return tok.ledger.totalSupply }
    130
    131// KnownAccounts returns the number of known accounts in the bank.
    132func (tok Token) KnownAccounts() int { return tok.ledger.balances.Size() }
    133
    134// ID returns the Identifier of the token.
    135// It is composed of the original realm, the symbol, and the provided id.
    136func (tok *Token) ID() string {
    137 return tok.id
    138}
    139
    140// HasAddr checks if the specified address is a known account in the bank.
    141func (tok Token) HasAddr(addr address) bool {
    142 return tok.ledger.hasAddr(addr)
    143}
    144
    145// BalanceOf returns the balance of the specified address.
    146func (tok Token) BalanceOf(addr address) int64 {
    147 return tok.ledger.balanceOf(addr)
    148}
    149
    150// Allowance returns the allowance of the specified owner and spender.
    151func (tok Token) Allowance(owner, spender address) int64 {
    152 return tok.ledger.allowance(owner, spender)
    153}
    154
    155func (tok Token) RenderHome() string {
    156 str := ""
    157 str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol)
    158 str += ufmt.Sprintf("* **Decimals**: %d\n", tok.decimals)
    159 str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply)
    160 str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts())
    161 return str
    162}
    163
    164// SpendAllowance decreases the allowance of the specified owner and spender.
    165func (led *PrivateLedger) SpendAllowance(owner, spender address, amount int64) error {
    166 if !owner.IsValid() || !spender.IsValid() {
    167 return ErrInvalidAddress
    168 }
    169
    170 if amount < 0 {
    171 return ErrInvalidAmount
    172 }
    173 // do nothing
    174 if amount == 0 {
    175 return nil
    176 }
    177
    178 currentAllowance := led.allowance(owner, spender)
    179 if currentAllowance < amount {
    180 return ErrInsufficientAllowance
    181 }
    182
    183 key := allowanceKey(owner, spender)
    184 newAllowance := overflow.Sub64p(currentAllowance, amount)
    185
    186 if newAllowance == 0 {
    187 led.allowances.Remove(key)
    188 } else {
    189 led.allowances.Set(key, newAllowance)
    190 }
    191
    192 return nil
    193}
    194
    195// Transfer transfers tokens from the specified from address to the specified to address.
    196func (led *PrivateLedger) Transfer(from, to address, amount int64) error {
    197 if !from.IsValid() {
    198 return ErrInvalidAddress
    199 }
    200 if !to.IsValid() {
    201 return ErrInvalidAddress
    202 }
    203 if from == to {
    204 return ErrCannotTransferToSelf
    205 }
    206 if amount < 0 {
    207 return ErrInvalidAmount
    208 }
    209
    210 var (
    211 toBalance = led.balanceOf(to)
    212 fromBalance = led.balanceOf(from)
    213 )
    214
    215 if fromBalance < amount {
    216 return ErrInsufficientBalance
    217 }
    218
    219 var (
    220 newToBalance = overflow.Add64p(toBalance, amount)
    221 newFromBalance = overflow.Sub64p(fromBalance, amount)
    222 )
    223
    224 led.balances.Set(string(to), newToBalance)
    225
    226 if newFromBalance == 0 {
    227 led.balances.Remove(string(from))
    228 } else {
    229 led.balances.Set(string(from), newFromBalance)
    230 }
    231
    232 chain.Emit(
    233 TransferEvent,
    234 "token", led.token.ID(),
    235 "from", from.String(),
    236 "to", to.String(),
    237 "value", strconv.Itoa(int(amount)),
    238 )
    239
    240 return nil
    241}
    242
    243// TransferFrom transfers tokens from the specified owner to the specified to address.
    244// It first checks if the owner has sufficient balance and then decreases the allowance.
    245func (led *PrivateLedger) TransferFrom(owner, spender, to address, amount int64) error {
    246 if amount < 0 {
    247 return ErrInvalidAmount
    248 }
    249
    250 if !owner.IsValid() || !to.IsValid() {
    251 return ErrInvalidAddress
    252 }
    253
    254 if owner == to {
    255 return ErrCannotTransferToSelf
    256 }
    257
    258 if led.balanceOf(owner) < amount {
    259 return ErrInsufficientBalance
    260 }
    261
    262 // The check above guarantees that Transfer will succeed, ensuring
    263 // atomicity for the subsequent operations.
    264 if err := led.SpendAllowance(owner, spender, amount); err != nil {
    265 return err
    266 }
    267
    268 if err := led.Transfer(owner, to, amount); err != nil {
    269 return err
    270 }
    271
    272 return nil
    273}
    274
    275// Approve sets the allowance of the specified owner and spender.
    276func (led *PrivateLedger) Approve(owner, spender address, amount int64) error {
    277 if !owner.IsValid() || !spender.IsValid() {
    278 return ErrInvalidAddress
    279 }
    280 if amount < 0 {
    281 return ErrInvalidAmount
    282 }
    283
    284 led.allowances.Set(allowanceKey(owner, spender), amount)
    285
    286 chain.Emit(
    287 ApprovalEvent,
    288 "token", led.token.ID(),
    289 "owner", string(owner),
    290 "spender", string(spender),
    291 "value", strconv.Itoa(int(amount)),
    292 )
    293
    294 return nil
    295}
    296
    297// Mint increases the total supply of the token and adds the specified amount to the specified address.
    298func (led *PrivateLedger) Mint(addr address, amount int64) error {
    299 if !addr.IsValid() {
    300 return ErrInvalidAddress
    301 }
    302 if amount < 0 {
    303 return ErrInvalidAmount
    304 }
    305
    306 // limit amount to MaxInt64 - totalSupply
    307 if amount > overflow.Sub64p(math.MaxInt64, led.totalSupply) {
    308 return ErrMintOverflow
    309 }
    310
    311 led.totalSupply += amount
    312 currentBalance := led.balanceOf(addr)
    313 newBalance := overflow.Add64p(currentBalance, amount)
    314
    315 led.balances.Set(string(addr), newBalance)
    316
    317 chain.Emit(
    318 TransferEvent,
    319 "token", led.token.ID(),
    320 "from", "",
    321 "to", string(addr),
    322 "value", strconv.Itoa(int(amount)),
    323 )
    324
    325 return nil
    326}
    327
    328// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
    329func (led *PrivateLedger) Burn(addr address, amount int64) error {
    330 if !addr.IsValid() {
    331 return ErrInvalidAddress
    332 }
    333 if amount < 0 {
    334 return ErrInvalidAmount
    335 }
    336
    337 currentBalance := led.balanceOf(addr)
    338 if currentBalance < amount {
    339 return ErrInsufficientBalance
    340 }
    341
    342 led.totalSupply = overflow.Sub64p(led.totalSupply, amount)
    343 newBalance := overflow.Sub64p(currentBalance, amount)
    344
    345 if newBalance == 0 {
    346 led.balances.Remove(string(addr))
    347 } else {
    348 led.balances.Set(string(addr), newBalance)
    349 }
    350
    351 chain.Emit(
    352 TransferEvent,
    353 "token", led.token.ID(),
    354 "from", string(addr),
    355 "to", "",
    356 "value", strconv.Itoa(int(amount)),
    357 )
    358
    359 return nil
    360}
    361
    362// hasAddr checks if the specified address is a known account in the ledger.
    363func (led PrivateLedger) hasAddr(addr address) bool {
    364 return led.balances.Has(addr.String())
    365}
    366
    367// balanceOf returns the balance of the specified address.
    368func (led PrivateLedger) balanceOf(addr address) int64 {
    369 balance := led.balances.Get(addr.String())
    370 if balance == nil {
    371 return 0
    372 }
    373 return balance.(int64)
    374}
    375
    376// allowance returns the allowance of the specified owner and spender.
    377func (led PrivateLedger) allowance(owner, spender address) int64 {
    378 allowance := led.allowances.Get(allowanceKey(owner, spender))
    379 if allowance == nil {
    380 return 0
    381 }
    382 return allowance.(int64)
    383}
    384
    385// allowanceKey returns the key for the allowance of the specified owner and spender.
    386func allowanceKey(owner, spender address) string {
    387 return owner.String() + ":" + spender.String()
    388}
    389