testhost.gno
4.37 Kb · 157 lines
1package vmkit
2
3import "gno.land/p/nt/avl/v0"
4
5// TestHost is a deterministic in-memory [Host] for unit tests and for
6// measuring a machine without a chain under it.
7//
8// Nothing here reads the chain, so a test that uses it produces the same
9// numbers on every host. Storage is an avl tree rather than a map because gno
10// map iteration order is unspecified, and [TestHost.Keys] has to be stable for
11// a test to assert on it.
12//
13// Send is denied unless [TestHost.Grant] funded a budget, which is how the
14// capability rule gets tested: a guest that tries to send without a grant must
15// come back [Trapped], not silently succeed.
16type TestHost struct {
17 caller address
18 origin address
19 now int64
20 height int64
21
22 store *avl.Tree // hex key -> []byte
23
24 in []byte
25 out []byte
26 events []string
27 logs []string
28
29 sendBudget int64
30 sends []Transfer
31}
32
33// Transfer records one successful [TestHost.Send].
34type Transfer struct {
35 To address
36 Amount int64
37}
38
39// NewTestHost returns a host with no capabilities granted, height 1, time 0,
40// and empty input.
41func NewTestHost() *TestHost {
42 return &TestHost{
43 height: 1,
44 store: avl.NewTree(),
45 }
46}
47
48// WithCaller sets the address the guest sees as its caller and origin.
49func (h *TestHost) WithCaller(a address) *TestHost {
50 h.caller, h.origin = a, a
51 return h
52}
53
54// WithOrigin overrides the origin separately from the caller.
55func (h *TestHost) WithOrigin(a address) *TestHost { h.origin = a; return h }
56
57// WithTime sets the block time in Unix seconds.
58func (h *TestHost) WithTime(t int64) *TestHost { h.now = t; return h }
59
60// WithHeight sets the block height.
61func (h *TestHost) WithHeight(n int64) *TestHost { h.height = n; return h }
62
63// WithInput sets the guest's call input.
64func (h *TestHost) WithInput(p []byte) *TestHost {
65 h.in = make([]byte, len(p))
66 copy(h.in, p)
67 return h
68}
69
70// Grant funds the send capability with a budget in ugnot. Without it, Send
71// returns [ErrNotGranted].
72func (h *TestHost) Grant(budget int64) *TestHost { h.sendBudget = budget; return h }
73
74func (h *TestHost) Caller() address { return h.caller }
75func (h *TestHost) Origin() address { return h.origin }
76func (h *TestHost) Now() int64 { return h.now }
77func (h *TestHost) Height() int64 { return h.height }
78func (h *TestHost) Input() []byte { return h.in }
79
80func (h *TestHost) Get(key []byte) []byte {
81 v := h.store.Get(hexKey(key))
82 if v == nil {
83 return nil
84 }
85 stored := v.([]byte)
86 out := make([]byte, len(stored))
87 copy(out, stored)
88 return out
89}
90
91func (h *TestHost) Set(key, val []byte) {
92 cp := make([]byte, len(val))
93 copy(cp, val)
94 h.store.Set(hexKey(key), cp)
95}
96
97func (h *TestHost) Output(p []byte) { h.out = append(h.out, p...) }
98
99func (h *TestHost) Emit(typ string, kv ...string) {
100 line := typ
101 for i := 0; i+1 < len(kv); i += 2 {
102 line += " " + kv[i] + "=" + kv[i+1]
103 }
104 h.events = append(h.events, line)
105}
106
107func (h *TestHost) Send(to address, amount int64) error {
108 if amount <= 0 || amount > h.sendBudget {
109 return ErrNotGranted
110 }
111 h.sendBudget -= amount
112 h.sends = append(h.sends, Transfer{To: to, Amount: amount})
113 return nil
114}
115
116func (h *TestHost) Log(msg string) { h.logs = append(h.logs, msg) }
117
118// Out returns everything the guest has written, as bytes.
119func (h *TestHost) Out() []byte { return h.out }
120
121// OutString returns everything the guest has written, as a string.
122func (h *TestHost) OutString() string { return string(h.out) }
123
124// ResetOut discards the output buffer, so one host can measure several runs.
125func (h *TestHost) ResetOut() { h.out = nil }
126
127// Events returns the rendered events, in emission order.
128func (h *TestHost) Events() []string { return h.events }
129
130// Logs returns the diagnostic lines, in emission order.
131func (h *TestHost) Logs() []string { return h.logs }
132
133// Sends returns the transfers that succeeded, in order.
134func (h *TestHost) Sends() []Transfer { return h.sends }
135
136// Keys returns every storage key the guest wrote, hex-encoded, in sorted
137// order.
138func (h *TestHost) Keys() []string {
139 out := []string{}
140 h.store.Iterate("", "", func(k string, _ any) bool {
141 out = append(out, k)
142 return false
143 })
144 return out
145}
146
147const hexDigits = "0123456789abcdef"
148
149// hexKey encodes a raw key as hex so that arbitrary bytes, including a zero
150// byte, survive being used as an avl key.
151func hexKey(key []byte) string {
152 out := make([]byte, 0, len(key)*2)
153 for _, c := range key {
154 out = append(out, hexDigits[c>>4], hexDigits[c&0x0f])
155 }
156 return string(out)
157}