Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

erc1155.gno

3.95 Kb · 156 lines
  1// Package erc1155 is an idiomatic gno.land port of the Solidity ERC-1155
  2// multi-token standard. A single realm tracks many distinct token ids, each
  3// with its own fungible balances. Balances are keyed by (owner, id) in an
  4// avl.Tree so Render can iterate deterministically.
  5package erc1155
  6
  7import (
  8	"strconv"
  9	"strings"
 10
 11	"chain"
 12	"chain/runtime/unsafe"
 13
 14	"gno.land/p/nt/avl/v0"
 15)
 16
 17// balances maps "<owner>:<id>" -> uint64 amount.
 18var balances avl.Tree
 19
 20// supply maps "<id>" -> uint64 total minted for that id.
 21var supply avl.Tree
 22
 23func balKey(owner address, id int) string {
 24	return owner.String() + ":" + strconv.Itoa(id)
 25}
 26
 27func supKey(id int) string {
 28	return strconv.Itoa(id)
 29}
 30
 31func getBalance(owner address, id int) uint64 {
 32	k := balKey(owner, id)
 33	if !balances.Has(k) {
 34		return 0
 35	}
 36	return balances.Get(k).(uint64)
 37}
 38
 39func setBalance(owner address, id int, amount uint64) {
 40	k := balKey(owner, id)
 41	if amount == 0 {
 42		balances.Remove(k)
 43		return
 44	}
 45	balances.Set(k, amount)
 46}
 47
 48func getSupply(id int) uint64 {
 49	k := supKey(id)
 50	if !supply.Has(k) {
 51		return 0
 52	}
 53	return supply.Get(k).(uint64)
 54}
 55
 56// Mint creates `amount` units of token `id` and credits them to `to`.
 57func Mint(cur realm, to address, id int, amount uint64) {
 58	if !to.IsValid() {
 59		panic("erc1155: mint to invalid address")
 60	}
 61	if amount == 0 {
 62		panic("erc1155: mint amount must be > 0")
 63	}
 64	setBalance(to, id, getBalance(to, id)+amount)
 65	supply.Set(supKey(id), getSupply(id)+amount)
 66
 67	operator := unsafe.PreviousRealm().Address()
 68	chain.Emit("Mint",
 69		"operator", operator.String(),
 70		"to", to.String(),
 71		"id", strconv.Itoa(id),
 72		"amount", strconv.FormatUint(amount, 10),
 73	)
 74}
 75
 76// TransferSingle moves `amount` of token `id` from the caller to `to`.
 77func TransferSingle(cur realm, to address, id int, amount uint64) {
 78	if !to.IsValid() {
 79		panic("erc1155: transfer to invalid address")
 80	}
 81	if amount == 0 {
 82		panic("erc1155: transfer amount must be > 0")
 83	}
 84	from := unsafe.PreviousRealm().Address()
 85	fromBal := getBalance(from, id)
 86	if fromBal < amount {
 87		panic("erc1155: insufficient balance")
 88	}
 89	setBalance(from, id, fromBal-amount)
 90	setBalance(to, id, getBalance(to, id)+amount)
 91
 92	chain.Emit("TransferSingle",
 93		"from", from.String(),
 94		"to", to.String(),
 95		"id", strconv.Itoa(id),
 96		"amount", strconv.FormatUint(amount, 10),
 97	)
 98}
 99
100// BalanceOf returns the amount of token `id` held by `owner`.
101func BalanceOf(owner address, id int) uint64 {
102	return getBalance(owner, id)
103}
104
105// TotalSupply returns the total amount minted of token `id`.
106func TotalSupply(id int) uint64 {
107	return getSupply(id)
108}
109
110// Render prints a balances table and a per-id supply table.
111func Render(path string) string {
112	var b strings.Builder
113	b.WriteString("# ERC-1155 Multi-Token\n\n")
114	b.WriteString("A single realm holding many fungible token ids. ")
115	b.WriteString("Balances are keyed by `(owner, id)`.\n\n")
116
117	b.WriteString("## Balances\n\n")
118	if balances.Size() == 0 {
119		b.WriteString("_No balances yet. Call `Mint` to create tokens._\n\n")
120	} else {
121		b.WriteString("| Owner | ID | Amount |\n")
122		b.WriteString("|---|---|---|\n")
123		balances.Iterate("", "", func(key string, value interface{}) bool {
124			owner, id := splitBalKey(key)
125			b.WriteString("| `" + owner + "` | " + id + " | " +
126				strconv.FormatUint(value.(uint64), 10) + " |\n")
127			return false
128		})
129		b.WriteString("\n")
130	}
131
132	b.WriteString("## Supply per ID\n\n")
133	if supply.Size() == 0 {
134		b.WriteString("_No tokens minted._\n")
135	} else {
136		b.WriteString("| ID | Total Supply |\n")
137		b.WriteString("|---|---|\n")
138		supply.Iterate("", "", func(key string, value interface{}) bool {
139			b.WriteString("| " + key + " | " +
140				strconv.FormatUint(value.(uint64), 10) + " |\n")
141			return false
142		})
143	}
144
145	return b.String()
146}
147
148// splitBalKey splits a "<owner>:<id>" balance key. The owner (a bech32
149// address) contains no ':', so the id is the substring after the last ':'.
150func splitBalKey(key string) (owner, id string) {
151	i := strings.LastIndex(key, ":")
152	if i < 0 {
153		return key, ""
154	}
155	return key[:i], key[i+1:]
156}