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.

definition.gno

5.58 Kb · 216 lines
  1package definition
  2
  3import (
  4	"chain/runtime/unsafe"
  5	"net/url"
  6	"sort"
  7	"strings"
  8
  9	"gno.land/p/akkadia/beta/accesscontrol"
 10	"gno.land/p/nt/bptree/v0"
 11	"gno.land/r/akkadia/beta/admin"
 12)
 13
 14type Field struct {
 15	Key   string
 16	Value string
 17}
 18
 19var definitions *bptree.BPTree
 20
 21func init() {
 22	definitions = bptree.NewBPTree32()
 23}
 24
 25// DeleteAll removes all definitions so an administrator can reload the catalog.
 26func DeleteAll(cur realm) {
 27	assertCanWrite(0, cur)
 28	definitions = bptree.NewBPTree32()
 29}
 30
 31// SetDefinitions accepts URL-escaped keys and query-encoded field objects as paired
 32// comma-separated strings, so MsgCall can pass a batch without realm DTOs.
 33func SetDefinitions(cur realm, keys, values string) {
 34	assertCanWrite(0, cur)
 35	if len(keys)+len(values) > 131071 {
 36		panic("call bytes exceeds 131071")
 37	}
 38	if keys == "" && values == "" {
 39		assertBatchCount(0)
 40	}
 41	if keys == "" || values == "" {
 42		panic("keys and values count mismatch")
 43	}
 44	type entry struct {
 45		Key    string
 46		Fields []Field
 47	}
 48	entries := []entry{}
 49	keyStart, keyEnd := 0, 0
 50	valueStart, valueEnd := 0, 0
 51	for {
 52		assertBatchCount(len(entries) + 1)
 53		for keyEnd < len(keys) && keys[keyEnd] != ',' {
 54			keyEnd++
 55		}
 56		for valueEnd < len(values) && values[valueEnd] != ',' {
 57			valueEnd++
 58		}
 59		if keyStart == keyEnd || valueStart == valueEnd {
 60			panic("empty entry token")
 61		}
 62		key, err := url.QueryUnescape(keys[keyStart:keyEnd])
 63		if err != nil {
 64			panic("invalid encoded key")
 65		}
 66		attributes, err := url.ParseQuery(values[valueStart:valueEnd])
 67		if err != nil {
 68			panic("invalid encoded fields")
 69		}
 70		if len(attributes) < 1 || len(attributes) > 32 {
 71			panic("field count must be between 1 and 32")
 72		}
 73		fields := make([]Field, 0, len(attributes))
 74		for name, fieldValues := range attributes {
 75			if len(fieldValues) != 1 {
 76				panic("duplicate field key")
 77			}
 78			fields = append(fields, Field{name, fieldValues[0]})
 79		}
 80		entries = append(entries, entry{key, fields})
 81		keyDone := keyEnd >= len(keys)
 82		valueDone := valueEnd >= len(values)
 83		if keyDone != valueDone {
 84			panic("keys and values count mismatch")
 85		}
 86		if keyDone {
 87			break
 88		}
 89		keyEnd++
 90		valueEnd++
 91		keyStart, valueStart = keyEnd, valueEnd
 92	}
 93	// Prepare every final record before writing any state.
 94	prepared := make([][]Field, len(entries))
 95	batchBytes := 0
 96	for i, entry := range entries {
 97		assertIdentifier(entry.Key, "key", 128)
 98		batchBytes += len(entry.Key) + assertFields(entry.Fields)
 99		if batchBytes > 65536 {
100			panic("batch bytes exceeds 65536")
101		}
102		for j := 0; j < i; j++ {
103			if entries[j].Key == entry.Key {
104				panic("duplicate entry")
105			}
106		}
107		fields, _ := getFields(entry.Key)
108		for _, update := range entry.Fields {
109			found := false
110			for j := range fields {
111				if fields[j].Key == update.Key {
112					fields[j].Value = update.Value
113					found = true
114					break
115				}
116			}
117			if !found {
118				fields = append(fields, update)
119			}
120		}
121		assertFields(fields)
122		// At most 32 fields; insertion sort avoids another persistent index.
123		for j := 1; j < len(fields); j++ {
124			for k := j; k > 0 && fields[k].Key < fields[k-1].Key; k-- {
125				fields[k], fields[k-1] = fields[k-1], fields[k]
126			}
127		}
128		prepared[i] = fields
129	}
130	for i, entry := range entries {
131		definitions.Set(entry.Key, prepared[i])
132	}
133}
134
135// Get returns ordered field records. An empty list means the key is absent;
136// stored definitions always contain at least one field.
137func Get(key string) []map[string]string {
138	fields, _ := getFields(key)
139	result := make([]map[string]string, 0, len(fields))
140	for _, field := range fields {
141		result = append(result, map[string]string{"key": field.Key, "value": field.Value})
142	}
143	return result
144}
145
146func getFields(key string) (fields []Field, found bool) {
147	if !definitions.Has(key) {
148		return nil, false
149	}
150	stored := definitions.Get(key).([]Field)
151	fields = make([]Field, len(stored))
152	copy(fields, stored)
153	return fields, true
154}
155
156// GetField includes "value" even when it is empty. An empty map means absent.
157func GetField(key, fieldKey string) map[string]string {
158	fields, _ := getFields(key)
159	for _, field := range fields {
160		if field.Key == fieldKey {
161			return map[string]string{"value": field.Value}
162		}
163	}
164	return map[string]string{}
165}
166
167func GetKeyCount() int { return definitions.Size() }
168
169func ListKeys(page, count int) []string {
170	assertPage(page, count)
171	result := []string{}
172	// Compare before multiplication so an extreme page cannot overflow.
173	if definitions.Size() == 0 || page-1 > (definitions.Size()-1)/count {
174		return result
175	}
176	// Sort a query-local view so existing records keep their original storage keys.
177	ordered := make([]string, 0, definitions.Size())
178	definitions.Iterate("", "", func(key string, _ any) bool {
179		numeric := true
180		for _, r := range key {
181			if r < '0' || r > '9' {
182				numeric = false
183				break
184			}
185		}
186		if numeric {
187			// Keys are at most 128 bytes. Padding avoids integer overflow; the
188			// original key breaks ties such as "01" and "1" without merging them.
189			ordered = append(ordered, "0"+strings.Repeat("0", 128-len(key))+key+":"+key)
190		} else {
191			ordered = append(ordered, "1:"+key)
192		}
193		return false
194	})
195	sort.Strings(ordered)
196	start := (page - 1) * count
197	end := start + count
198	if end > len(ordered) {
199		end = len(ordered)
200	}
201	for _, key := range ordered[start:end] {
202		result = append(result, key[strings.IndexByte(key, ':')+1:])
203	}
204	return result
205}
206
207func assertCanWrite(_ int, rlm realm) {
208	accesscontrol.AssertIsAdmin(0, rlm, admin.IsAdmin)
209	// MsgRun has the signer address but an ephemeral /e/<address>/run path.
210	if !rlm.Previous().IsUser() {
211		panic("direct user call or run required")
212	}
213	if len(unsafe.OriginSend()) != 0 {
214		panic("attached coins not allowed")
215	}
216}