doc.gno
7.42 Kb · 180 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// - **Authorized Caller Management**: Dynamic addition, update, and removal
35// of authorized callers with specific permissions.
36//
37// ## Permission Model
38//
39// The package defines one assignable permission level:
40//
41// - **Write (1)**: Allowed to call `Set` and `Delete`.
42//
43// The zero value of `Permission` is reserved and rejected by registration APIs.
44// Code callers not in the authorized-caller table have no write access. Current
45// user/EOA realms bypass the ACL for Set and Delete after the live-frame check.
46// Read methods do not enforce caller permissions; realm-level APIs must gate
47// sensitive reads themselves. The domain address (owner) is always treated as
48// authorized and cannot be removed.
49//
50// ## Realm Threading
51//
52// Every mutating method takes two leading parameters:
53//
54// func (k *kvStore) Set(_ int, rlm realm, key string, value any) error
55//
56// The leading `_ int` is a deliberate sentinel: at the call site it shows up
57// as a `0`, making it visually obvious that a `realm` value is being threaded
58// through. `rlm` is the live crossing frame, and `rlm.IsCurrent()` is checked
59// first in every mutating method to reject stale or spoofed tokens.
60//
61// The v1 -> v2 mapping for the two realm-identity expressions is straightforward:
62//
63// - `runtime.CurrentRealm().Address()` -> `rlm.Address()` (the current realm)
64// - `runtime.PreviousRealm().Address()` -> `rlm.Previous().Address()` (the caller)
65//
66// Which one a given method checks is a per-method domain decision, not a v2
67// framework rule. The store currently checks:
68//
69// - `Set` / `Delete`: `rlm.Address()` (the realm performing the write, i.e.
70// the domain realm calling into the store) against the write ACL for code
71// callers. Current user/EOA realms (`!rlm.IsCode()`) bypass the ACL after
72// `rlm.IsCurrent()` validation, so they can populate or delete state.
73// - `AddAuthorizedCaller`, `UpdateAuthorizedCaller`,
74// `RemoveAuthorizedCaller`: `rlm.Address()` (the current realm) against
75// the domain address.
76//
77// ## Workflow
78//
79// 1. **Initialization**: Create a store with `NewKVStore(domainAddress)`.
80// 2. **Data Operations**: Use `Set` / `Get` (and typed getters) for I/O.
81// 3. **Permission Management**: From the domain realm, call
82// `AddAuthorizedCaller` to grant write access to additional realms.
83// 4. **Type-Safe Retrieval**: Prefer typed getters (`GetInt64`, `GetString`,
84// etc.) over raw `Get` whenever the stored type is known.
85//
86// ## Example Usage
87//
88// ```gno
89// package examplerealm
90//
91// import (
92//
93// "gno.land/p/gnoswap/store/v1"
94//
95// )
96//
97// func Configure(cur realm, routerAddr address) error {
98// // Create a KVStore owned by the current (pool) realm.
99// kv := store.NewKVStore(cur.Address())
100//
101// // Persist some values. `0, cur` threads the live realm frame through.
102// if err := kv.Set(0, cur, "totalLiquidity", uint64(1_000_000)); err != nil {
103// return err
104// }
105// if err := kv.Set(0, cur, "poolName", "ETH-USDC"); err != nil {
106// return err
107// }
108//
109// // Grant write access to the router realm.
110// if err := kv.AddAuthorizedCaller(0, cur, routerAddr, store.Write); err != nil {
111// return err
112// }
113//
114// // Reads are public -- no realm needed.
115// liquidity, err := kv.GetUint64("totalLiquidity")
116// if err != nil {
117// return err
118// }
119// _ = liquidity
120//
121// return nil
122// }
123//
124// ```
125//
126// ## Permission Checks at a Glance
127//
128// The store automatically verifies permissions during operations:
129//
130// - **Read Operations** (`Get`, `GetInt64`, `GetString`, ...): no permission
131// check; reads are public within the package.
132// - **Write Operations** (`Set`, `Delete`): require the calling realm --
133// `rlm.Address()` -- to be in the write ACL when the caller is code.
134// Current user/EOA realms (`!rlm.IsCode()`) bypass the ACL after the live
135// frame check.
136// - **Management Operations** (`AddAuthorizedCaller`,
137// `UpdateAuthorizedCaller`, `RemoveAuthorizedCaller`): require the
138// current realm -- `rlm.Address()` -- to equal the domain address.
139//
140// ## Key Prefixing
141//
142// Internally, all keys are prefixed with the domain address to keep domains
143// isolated:
144//
145// Stored key: "{domainAddress}:{userKey}"
146//
147// This prevents key collisions between domains that share a runtime.
148//
149// ## Errors
150//
151// The package defines several error types:
152//
153// - `ErrWritePermissionDenied` - the calling code realm (`rlm.Address()`) is
154// not in the write ACL. Current user/EOA realms bypass the ACL for Set and
155// Delete after `rlm.IsCurrent()` validation.
156// - `ErrUpdatePermissionDenied` - ACL change attempted from a current realm
157// (`rlm.Address()`) other than the domain address.
158// - `ErrAuthorizedCallerAlreadyRegistered` - Adding a caller that already
159// exists.
160// - `ErrAuthorizedCallerNotFound` - Updating or removing a caller that was
161// never registered.
162// - `ErrInvalidPermission` - Permission value other than `Write` was passed.
163// - `ErrFailedCast` - Typed getter saw a value whose runtime type does not
164// match.
165// - `ErrSpoofedRealm` - The supplied `realm` is not the live crossing frame.
166//
167// ## Limitations and Considerations
168//
169// - All stored values are of type `any`, so retrieval requires either a
170// typed getter or a cast.
171// - Read methods are not permission-gated. Add realm-level checks for
172// sensitive reads.
173// - ACL management can only be performed by the domain realm itself.
174// - The domain address is implicitly authorized and cannot be removed.
175// - Keys are automatically prefixed; callers should not encode the domain
176// address into their own keys.
177//
178// Package store is intended for use in Gno smart contracts that need
179// isolated, write-gated storage for distinct protocol components.
180package store