doc.gno
6.86 Kb · 169 lines
1// Package store provides a domain-specific key-value storage system with
2// permission-based write access control for Gno smart contracts.
3//
4// ## Overview
5//
6// Each domain (e.g., pool, position, router, staker) creates its own KVStore
7// instance bound to a unique domain address. That domain address is the
8// primary authority over the store: it can write, manage the authorized-writer
9// set, and is itself implicitly authorized as a writer.
10//
11// Key components of this package:
12//
13// 1. **KVStore Interface** - The contract for domain-specific storage operations.
14// 2. **kvStore Implementation** - Concrete implementation with permission
15// management and data storage.
16// 3. **Write-only Permission System** - Reads are public within the package;
17// only writes (Set, Delete, ACL changes) are gated.
18// 4. **Type-Safe Getters** - Typed getter methods with runtime type casting
19// and validation.
20//
21// ## Key Features
22//
23// - **Domain Isolation**: Each domain has its own isolated storage space
24// identified by its domain address. Keys are internally prefixed with that
25// address so domains cannot collide.
26// - **Per-Realm Write ACL**: The store maintains an explicit set of realms
27// authorized to write. The domain owner is in the set by default.
28// - **Type-Safe Operations**: Specialized getters (`GetInt64`, `GetString`,
29// `GetAddress`, ...) with automatic casting and structured errors.
30// - **Realm-Frame Auth Checks**: Every write call accepts a `realm` value
31// representing the live crossing frame; the store verifies that frame
32// before consulting the ACL, defending against replayed or captured
33// `realm` values.
34//
35// ## Permission Model
36//
37// There is exactly one assignable permission level:
38//
39// - **Write**: Allowed to call `Set` and `Delete`.
40//
41// The zero value of `Permission` is reserved and rejected by registration APIs.
42// The domain address is always treated as authorized and cannot be removed.
43//
44// ## Realm Threading
45//
46// Every mutating method takes two leading parameters:
47//
48// func (k *kvStore) Set(_ int, rlm realm, key string, value any) error
49//
50// The leading `_ int` is a deliberate sentinel: at the call site it shows up
51// as a `0`, making it visually obvious that a `realm` value is being threaded
52// through. `rlm` is the live crossing frame, and `rlm.IsCurrent()` is checked
53// first in every mutating method to reject stale or spoofed tokens.
54//
55// The v1 -> v2 mapping for the two realm-identity expressions is straightforward:
56//
57// - `runtime.CurrentRealm().Address()` -> `rlm.Address()` (the current realm)
58// - `runtime.PreviousRealm().Address()` -> `rlm.Previous().Address()` (the caller)
59//
60// Which one a given method checks is a per-method domain decision, not a v2
61// framework rule. The store currently checks:
62//
63// - `Set` / `Delete`: `rlm.Previous().Address()` (the caller) against the
64// write ACL. Set additionally bypasses the ACL when `rlm.IsCode()` is
65// false so EOA / user-realm callers (deploy, tests) can populate state
66// without prior registration; Delete has no such bypass.
67// - `AddAuthorizedCaller`, `UpdateAuthorizedCaller`,
68// `RemoveAuthorizedCaller`: `rlm.Address()` (the current realm) against
69// the domain address.
70//
71// ## Workflow
72//
73// 1. **Initialization**: Create a store with `NewKVStore(domainAddress)`.
74// 2. **Data Operations**: Use `Set` / `Get` (and typed getters) for I/O.
75// 3. **Permission Management**: From the domain realm, call
76// `AddAuthorizedCaller` to grant write access to additional realms.
77// 4. **Type-Safe Retrieval**: Prefer typed getters (`GetInt64`, `GetString`,
78// etc.) over raw `Get` whenever the stored type is known.
79//
80// ## Example Usage
81//
82// ```gno
83// package examplerealm
84//
85// import (
86//
87// "gno.land/p/gnoswap/store"
88//
89// )
90//
91// func Configure(cur realm, routerAddr address) error {
92// // Create a KVStore owned by the current (pool) realm.
93// kv := store.NewKVStore(cur.Address())
94//
95// // Persist some values. `0, cur` threads the live realm frame through.
96// if err := kv.Set(0, cur, "totalLiquidity", uint64(1_000_000)); err != nil {
97// return err
98// }
99// if err := kv.Set(0, cur, "poolName", "ETH-USDC"); err != nil {
100// return err
101// }
102//
103// // Grant write access to the router realm.
104// if err := kv.AddAuthorizedCaller(0, cur, routerAddr, store.Write); err != nil {
105// return err
106// }
107//
108// // Reads are public -- no realm needed.
109// liquidity, err := kv.GetUint64("totalLiquidity")
110// if err != nil {
111// return err
112// }
113// _ = liquidity
114//
115// return nil
116// }
117//
118// ```
119//
120// ## Permission Checks at a Glance
121//
122// - **Read Operations** (`Get`, `GetInt64`, `GetString`, ...): no permission
123// check; reads are public within the package.
124// - **Write Operations** (`Set`, `Delete`): require the caller --
125// `rlm.Previous().Address()` -- to be in the write ACL. Set additionally
126// bypasses the ACL when the immediate caller is an EOA / user realm
127// (`!rlm.IsCode()`); Delete has no such bypass.
128// - **Management Operations** (`AddAuthorizedCaller`,
129// `UpdateAuthorizedCaller`, `RemoveAuthorizedCaller`): require the
130// current realm -- `rlm.Address()` -- to equal the domain address.
131//
132// ## Key Prefixing
133//
134// Internally, all keys are prefixed with the domain address to keep domains
135// isolated:
136//
137// Stored key: "{domainAddress}:{userKey}"
138//
139// This prevents key collisions between domains that share a runtime.
140//
141// ## Errors
142//
143// - `ErrKeyNotFound` - Requested key does not exist.
144// - `ErrWritePermissionDenied` - the caller (`rlm.Previous().Address()`) is
145// not in the write ACL. For Set, this only fires when `rlm.IsCode()` is
146// true; the EOA / user-realm path bypasses the ACL.
147// - `ErrUpdatePermissionDenied` - ACL change attempted from a current realm
148// (`rlm.Address()`) other than the domain address.
149// - `ErrAuthorizedCallerAlreadyRegistered` - Adding a caller that already
150// exists.
151// - `ErrAuthorizedCallerNotFound` - Updating or removing a caller that was
152// never registered.
153// - `ErrInvalidPermission` - Permission value other than `Write` was passed.
154// - `ErrFailedCast` - Typed getter saw a value whose runtime type does not
155// match.
156// - `errSpoofedRealm` - The supplied `realm` is not the live crossing frame.
157//
158// ## Limitations and Considerations
159//
160// - All stored values are of type `any`, so retrieval requires either a
161// typed getter or a cast.
162// - ACL management can only be performed by the domain realm itself.
163// - The domain address is implicitly authorized and cannot be removed.
164// - Keys are automatically prefixed; callers should not encode the domain
165// address into their own keys.
166//
167// Package store is intended for use in Gno smart contracts that need
168// isolated, write-gated storage for distinct protocol components.
169package store