rust_test.gno
3.39 Kb · 99 lines
1package riscv
2
3import (
4 "testing"
5
6 "gno.land/p/moul/x/vm/vmkit/v0"
7 "gno.land/p/nt/uassert/v0"
8)
9
10// The demo the design issue actually asked for: Rust code, compiled by rustc,
11// running on gno.land, moving a balance from one account to another.
12//
13// The image is guests.gno's, built from tools/riscv-guests/ledger. It is worth
14// more than the C guest for one reason: it links Rust's real core and
15// compiler_builtins, so this exercises the standard library's own software
16// arithmetic rather than only what the compiler emitted for my source.
17
18func runLedger(t *testing.T, in string) (string, vmkit.Status, *Machine) {
19 t.Helper()
20 m, err := NewMachine(GuestLedger(), entry)
21 uassert.NoError(t, err)
22 if m == nil {
23 return "", vmkit.Trapped, nil
24 }
25 h := vmkit.NewTestHost().WithInput([]byte(in))
26 _, status := m.Step(h, 5000000)
27 return string(h.Out()), status, m
28}
29
30func TestRustLedgerTransfers(t *testing.T) {
31 out, status, _ := runLedger(t,
32 "mint alice 100\nmint bob 50\nsend alice bob 30\n")
33 uassert.Equal(t, "halted", status.String())
34 uassert.Equal(t, "alice 70\nbob 80\n", out)
35}
36
37func TestRustLedgerMintBurnAndBlankLines(t *testing.T) {
38 out, status, _ := runLedger(t, "mint a 5\n\n \nburn a 2\n")
39 uassert.Equal(t, "halted", status.String())
40 uassert.Equal(t, "a 3\n", out)
41
42 out, status, _ = runLedger(t, "")
43 uassert.Equal(t, "halted", status.String())
44 uassert.Equal(t, "", out)
45}
46
47// A refusal is halted, not trapped, and it names the line. This is the
48// semantic the whole VM is built around: the program said no, the machine did
49// not break, and a chain has to be able to tell a user which one happened.
50func TestRustLedgerRefusalsAreHaltsNotTraps(t *testing.T) {
51 cases := []struct{ in, want string }{
52 {"mint a 1\nsend a b 9\n", "line 2: insufficient balance\n"},
53 {"send nobody b 1\n", "line 1: unknown sender\n"},
54 {"burn ghost 1\n", "line 1: unknown account\n"},
55 {"frobnicate\n", "line 1: unknown command\n"},
56 {"mint a x\n", "line 1: bad amount\n"},
57 }
58 for _, c := range cases {
59 out, status, m := runLedger(t, c.in)
60 uassert.Equal(t, "halted", status.String(), c.in+": "+m.Trap())
61 uassert.Equal(t, c.want, out)
62 uassert.Equal(t, uint64(1), uint64(m.Registers()[10]), "a refusal exits 1")
63 }
64}
65
66// 64-bit arithmetic on a 32-bit machine goes through compiler_builtins, and
67// printing a balance above 2^32 is the path that proves __udivdi3 ran.
68func TestRustLedgerDoes64BitArithmetic(t *testing.T) {
69 // Nine-digit mints, accumulated past what 32 bits holds.
70 in := ""
71 for i := 0; i < 6; i++ {
72 in += "mint whale 999999999\n"
73 }
74 out, status, _ := runLedger(t, in)
75 uassert.Equal(t, "halted", status.String())
76 uassert.Equal(t, "whale 5999999994\n", out)
77}
78
79// Compiled Rust pauses and resumes like anything else, including in the middle
80// of a library routine it never asked to call.
81func TestRustLedgerSurvivesSlicing(t *testing.T) {
82 image := GuestLedger()
83 h := vmkit.NewTestHost().WithInput([]byte("mint alice 100\nsend alice bob 30\n"))
84
85 sliced, err := NewMachine(image, entry)
86 uassert.NoError(t, err)
87 status := vmkit.Running
88 slices := 0
89 for i := 0; i < 4000 && status == vmkit.Running; i++ {
90 var fresh Machine
91 uassert.NoError(t, fresh.Restore(sliced.Snapshot()))
92 _, status = fresh.Step(h, 17)
93 sliced = &fresh
94 slices++
95 }
96 uassert.Equal(t, "halted", status.String())
97 uassert.True(t, slices > 20, "a sliced run must actually have been sliced")
98 uassert.Equal(t, "alice 70\nbob 30\n", string(h.Out()))
99}