Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}