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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

core.gno

6.61 Kb · 192 lines
  1// Package core is the platform's immutable ledger (design §2.5.2): every
  2// collection, its official grc721 token and royalty, its provenance
  3// (commit–reveal v2), the engine registry, fees and the admin timelock.
  4//
  5// What core guarantees, whoever holds the admin key:
  6//
  7//   - I-1  admin cannot move, approve or burn a token, and holds no funds;
  8//     the op list is a closed set of parameters (admin.gno).
  9//   - I-2  pause only gates create, mint and trade. Transfer, burn,
 10//     finalize, distribute and every cancel stay open. A pause expires by
 11//     itself, cannot be renewed while it runs, and each scope has a budget
 12//     of MaxPauseBlocks per PauseWindowBlocks (SA-L2).
 13//   - I-5  supply only grows through the collection's bound minter, never
 14//     past maxSupply, and a token id is never minted twice.
 15//   - I-6  after Launch the engine, params, supply, payees, sale window and
 16//     commitments are frozen.
 17//   - I-10/11  a collection becomes Final at most once, only with both
 18//     reveals or after revealDeadline, and that deadline is bounded, so
 19//     the pre-reveal transfer lock always ends.
 20//   - I-12 engine code never runs on a state-changing path, except the
 21//     creator's own CreateCollection (ValidateParams) and the engine's own
 22//     RegisterEngine (one sample render). Every exported non-crossing
 23//     function is read-only, so an engine cannot call back into a mutator.
 24//   - I-13 every stored string passed safe.AssertText.
 25//
 26// core imports no mutable realm: minters and engines are known only by
 27// package path and interface value.
 28package core
 29
 30import (
 31	"chain"
 32	"chain/runtime"
 33	"chain/runtime/unsafe"
 34	"strconv"
 35
 36	"gno.land/p/nt/avl/v0"
 37	"gno.land/p/nt/grc721/royalty/v0"
 38	"gno.land/p/nt/grc721/v0"
 39
 40	"gno.land/p/g1pq6pxhgxk2uw5shg5le6glfydpxcu74k0zh069/sinopia/safe/v1"
 41	"gno.land/p/g1pq6pxhgxk2uw5shg5le6glfydpxcu74k0zh069/sinopia/types/v1"
 42)
 43
 44// Hard limits. They are constants on purpose: nobody, admin included, can
 45// raise them after deploy (block counts assume ~3.54 s blocks, RA M-1).
 46const (
 47	ProtocolFeeCapBps = 500           // 5% of any sale
 48	RoyaltyCapBps     = 1000          // 10% (RA M-3)
 49	CreateFeeCap      = 1_000_000_000 // 1,000 GNOT in ugnot
 50	MaxPayees         = 4
 51	MaxSupplyCap      = 10_000
 52	MaxMintPerTx      = 10    // hypothesis: measure n=10 gas/deposit on Onyx
 53	MaxReserveBps     = 1000  // creator reserve <= 10% of supply
 54	MaxSaleBlocks     = 732_000 // ~30 days
 55	RevealWindowMin   = 1_017   // ~1 hour
 56	RevealWindowMax   = 170_000 // ~7 days
 57	BeaconGraceBlocks = 254     // beacon may reveal first in the window's last ~15 min (< RevealWindowMin, so the creator always gets to go first)
 58	TimelockBlocks    = 48_800  // ~48 hours
 59	MaxPauseBlocks    = 195_000 // ~8 days: pause budget per scope and window
 60	PauseWindowBlocks = 732_000 // ~30 days (SA-L2)
 61
 62	MaxNameLen        = 64
 63	MaxDescriptionLen = 1024
 64	MaxParamsLen      = 1024
 65	MinSecretLen      = 32
 66	MaxSecretLen      = 256
 67	MaxPageSize       = 50
 68)
 69
 70// Provenance badges (design §4.3).
 71const (
 72	BadgeBoth        = "2/2"
 73	BadgeCreatorOnly = "creator-only"
 74	BadgeBeaconOnly  = "beacon-only"
 75	BadgeFallback    = "fallback"
 76)
 77
 78type collection struct {
 79	id, name, symbol, description string
 80	creator, pendingCreator       address
 81	engineID, params              string
 82	maxSupply                     int64
 83	nextTID                       int64 // next token id; minted so far = nextTID-1
 84	reserved                      int64
 85	royaltyBps                    int64
 86	primaryPayees, royaltyPayees  []types.Payee
 87	minter                        string
 88	state                         types.State
 89	startHeight, endHeight        int64
 90	revealWindow                  int64
 91	closeHeight                   int64 // sell-out height, or endHeight once time ran out
 92	useBeacon                     bool
 93	createdHeight                 int64
 94	primaryBps                    int64    // protocol fee snapshot at Launch (SA-I5)
 95	burnedCommits                 []string // commitments whose secret went public; never accepted again here (SA-L1)
 96	approvals                     avl.Tree // "t:"+tid or "o:"+owner+":"+operator -> int64 nonce (SA-L3)
 97	prov                          *provenance
 98	token                         *grc721.Token
 99	ledger                        *grc721.PrivateLedger
100	roy                           *royalty.Royalty
101	st                            *tokenState
102	mintedBy                      avl.Tree // address -> int64, lifetime, written on every mint (I-9)
103}
104
105func (c *collection) minted() int64 { return c.nextTID - 1 }
106
107var (
108	// selfPath is this realm's package path, captured once in init so read
109	// functions can derive sub-addresses without a cur.
110	selfPath string
111
112	collections avl.Tree // safe.PadInt(cid) -> *collection
113	byCreator   avl.Tree // creator + ":" + padded cid -> cid
114	collCount   int64
115)
116
117func init(cur realm) {
118	selfPath = cur.PkgPath()
119}
120
121const cidWidth = 10
122
123func collKey(cid string) string {
124	return safe.PadInt(safe.ParseID("cid", cid), cidWidth)
125}
126
127func mustCollection(cid string) *collection {
128	v := collections.Get(collKey(cid))
129	if v == nil {
130		panic("unknown collection " + strconv.Quote(cid))
131	}
132	return v.(*collection)
133}
134
135// stateAt is the effective state at height h; the stored state lags until
136// the next write materializes it (refresh).
137func stateAt(c *collection, h int64) types.State {
138	switch c.state {
139	case types.StateScheduled:
140		if h > c.endHeight {
141			return types.StateClosed
142		}
143		if h >= c.startHeight {
144			return types.StateMinting
145		}
146	case types.StateMinting:
147		if h > c.endHeight {
148			return types.StateClosed
149		}
150	}
151	return c.state
152}
153
154func refresh(c *collection, h int64) {
155	s := stateAt(c, h)
156	if s == c.state {
157		return
158	}
159	if s == types.StateClosed && c.closeHeight == 0 {
160		c.closeHeight = c.endHeight
161	}
162	c.state = s
163	chain.Emit("CollectionState", "cid", c.id, "state", s.String())
164}
165
166// revealDeadline is when anyone may finalize without the missing secrets.
167// It is at most endHeight + RevealWindowMax, which bounds the transfer
168// lock (I-11).
169func revealDeadline(c *collection) int64 {
170	closed := c.closeHeight
171	if closed == 0 {
172		closed = c.endHeight
173	}
174	return closed + c.revealWindow
175}
176
177func caller(cur realm) address {
178	return cur.Previous().Address()
179}
180
181// assertNoDeposit refuses coins on functions that take none, so nothing can
182// get stuck in core (RA L-1). The origin-send envelope belongs to the first
183// realm of the call; it is only ours to judge when a wallet called us.
184func assertNoDeposit(cur realm) {
185	if cur.Previous().IsUserCall() && !unsafe.OriginSend().IsZero() {
186		panic("this function does not accept coins")
187	}
188}
189
190func itoa(n int64) string { return strconv.FormatInt(n, 10) }
191
192func height() int64 { return runtime.ChainHeight() }