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.

draw.gno

3.51 Kb · 97 lines
 1package planes
 2
 3import (
 4	"chain/runtime"
 5	"crypto/sha256"
 6	"encoding/binary"
 7	"time"
 8)
 9
10// Planes are minted in a random order: each mint draws one of the ids not
11// minted yet. The id drawn is the token id and the row of the table, so
12// whoever draws 777 gets the SuperGnome.
13//
14// The pool of ids left is a byte string of two bytes per id, big-endian,
15// 1554 bytes at deploy: a slice of 777 int64 would cost several times the
16// storage. A draw takes the id at a random index and moves the last id into
17// its place (swap-remove), so every id comes out exactly once and a draw
18// costs the same at any point of the sale.
19//
20// The randomness is pseudo-random, from what a realm can see: the sha256 of
21// the block height, the block time, the minter, the count minted so far and
22// the hash of the previous draw. A validator could nudge the block time, a
23// user could try to time their block; nobody can see a draw and undo it,
24// because Mint only accepts direct user calls (a contract cannot call Mint,
25// look at the id and panic to try again). That is enough for this
26// collection; it is not for anything with money riding on the outcome.
27
28// pool holds the ids not minted yet, two bytes each. Its length over two is
29// how many are left.
30var pool []byte
31
32// mintedBits has one bit per id, set once the id is minted (burned or not):
33// what tells a burned plane from one not minted yet, without scanning pool.
34var mintedBits []byte
35
36// lastDraw is the hash of the previous draw, chained into the next.
37var lastDraw [32]byte
38
39// testDraw, when set by a test, replaces the hash to pick an index in a pool
40// of n ids. It is never set outside the tests.
41var testDraw func(n int) int
42
43// resetPool fills the pool with every id, from 777 down to 1, and seeds the
44// chain of draws from the realm's path.
45func resetPool(pkgPath string) {
46	pool = make([]byte, 2*supply)
47	for i := int64(0); i < supply; i++ {
48		binary.BigEndian.PutUint16(pool[2*i:], uint16(supply-i))
49	}
50	mintedBits = make([]byte, (supply+7)/8)
51	lastDraw = sha256.Sum256([]byte(pkgPath))
52}
53
54// left returns how many ids are not minted yet.
55func left() int64 {
56	return int64(len(pool) / 2)
57}
58
59// drawHash is the hash a draw picks its index from. It is a pure function of
60// its inputs, so that the tests can check what moves it.
61func drawHash(height, nanos int64, minter address, minted int64, prev [32]byte) [32]byte {
62	buf := make([]byte, 0, 8+8+len(minter)+8+32)
63	buf = binary.BigEndian.AppendUint64(buf, uint64(height))
64	buf = binary.BigEndian.AppendUint64(buf, uint64(nanos))
65	buf = append(buf, minter...)
66	buf = binary.BigEndian.AppendUint64(buf, uint64(minted))
67	buf = append(buf, prev[:]...)
68	return sha256.Sum256(buf)
69}
70
71// drawID takes one id out of the pool, at random, for minter, and marks it
72// minted. The pool must not be empty.
73func drawID(minter address) int64 {
74	n := len(pool) / 2
75	var i int
76	if testDraw != nil {
77		i = testDraw(n)
78	} else {
79		lastDraw = drawHash(runtime.ChainHeight(), time.Now().UnixNano(), minter, Minted(), lastDraw)
80		// The first 8 bytes modulo the pool size: the bias of a modulo of
81		// 2^64 by at most 777 is under 777/2^64, nothing.
82		i = int(binary.BigEndian.Uint64(lastDraw[:8]) % uint64(n))
83	}
84	id := int64(binary.BigEndian.Uint16(pool[2*i:]))
85	copy(pool[2*i:2*i+2], pool[2*(n-1):2*n])
86	pool = pool[:2*(n-1)]
87	mintedBits[(id-1)/8] |= 1 << uint((id-1)%8)
88	return id
89}
90
91// wasMinted reports whether id was ever minted, burned since or not.
92func wasMinted(id int64) bool {
93	if id < 1 || id > supply {
94		return false
95	}
96	return mintedBits[(id-1)/8]&(1<<uint((id-1)%8)) != 0
97}