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.

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}