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.

nativeify_test.gno

10.72 Kb · 272 lines
  1package nativeify
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"strings"
  7	"testing"
  8
  9	"gno.land/p/nym-moultest123/x/envelope/v0"
 10	"gno.land/p/nt/testutils/v0"
 11	"gno.land/p/nt/uassert/v0"
 12	"gno.land/r/nym-moultest123/x/grc20faucet/v0"
 13	"gno.land/r/nym-moultest123/x/nativereg/v0"
 14)
 15
 16// Two things about the harness shape every test below.
 17//
 18// testing.SetRealm governs only the crossing calls made from the frame that
 19// called it, so every account switch is inline in the test body.
 20//
 21// The BANK does not carry over between test functions while realm state does,
 22// so native balances, TotalCoin and the escrowed GRC20 cannot be compared
 23// across tests. Everything here is asserted as a delta inside one test.
 24
 25func TestNativeifyCreatesThePair(cur realm, t *testing.T) {
 26	alice := testutils.TestAddress("nf-alice")
 27	testing.SetRealm(testing.NewUserRealm(alice))
 28
 29	denom := Nativeify(cross(cur), grc20faucet.RedKey, "nred")
 30	uassert.Equal(t, "/gno.land/r/nym-moultest123/x/nativeify/v0:nred", denom)
 31	uassert.Equal(t, denom, Denom("nred"))
 32	uassert.Equal(t, grc20faucet.RedKey, TokenKey("nred"))
 33
 34	// The denom names this realm, so this realm is the only thing on the chain
 35	// that can describe it, and it did.
 36	e := nativereg.MustGet(denom)
 37	uassert.Equal(t, Path, e.Issuer)
 38	uassert.Equal(t, "nRED", e.Symbol)
 39	uassert.Equal(t, 4, e.Decimals, "the underlying's decimals travel as a display hint")
 40
 41	uassert.AbortsContains(t, cur, "already issues nred", func() {
 42		Nativeify(cross(cur), grc20faucet.BlueKey, "nred")
 43	})
 44	uassert.AbortsContains(t, cur, "already native here", func() {
 45		Nativeify(cross(cur), grc20faucet.RedKey, "nred2")
 46	})
 47	uassert.AbortsContains(t, cur, "unknown token", func() {
 48		Nativeify(cross(cur), "gno.land/r/nope/nope.NOPE", "nnope")
 49	})
 50}
 51
 52func TestBaseNameFollowsTheChainsRule(cur realm, t *testing.T) {
 53	bob := testutils.TestAddress("nf-bob")
 54	testing.SetRealm(testing.NewUserRealm(bob))
 55
 56	// The chain's own rule, applied here so the caller reads a sentence instead
 57	// of a banker stack trace: a lowercase letter, then 2 to 15 more lowercase
 58	// letters or digits.
 59	uassert.AbortsContains(t, cur, "is 2 characters", func() {
 60		Nativeify(cross(cur), grc20faucet.BlueKey, "ab")
 61	})
 62	uassert.AbortsContains(t, cur, "is 17 characters", func() {
 63		Nativeify(cross(cur), grc20faucet.BlueKey, strings.Repeat("a", 17))
 64	})
 65	uassert.AbortsContains(t, cur, "is not", func() {
 66		Nativeify(cross(cur), grc20faucet.BlueKey, "nBLUE")
 67	})
 68	uassert.AbortsContains(t, cur, "is not", func() {
 69		Nativeify(cross(cur), grc20faucet.BlueKey, "1blue")
 70	})
 71	uassert.AbortsContains(t, cur, "is not", func() {
 72		Nativeify(cross(cur), grc20faucet.BlueKey, "n-blue")
 73	})
 74
 75	// And one that is legal all the way through.
 76	uassert.Equal(t, "/gno.land/r/nym-moultest123/x/nativeify/v0:nblue1",
 77		Nativeify(cross(cur), grc20faucet.BlueKey, "nblue1"))
 78}
 79
 80func TestWrapNeedsAnAllowanceFirst(cur realm, t *testing.T) {
 81	carol := testutils.TestAddress("nf-carol")
 82	testing.SetRealm(testing.NewUserRealm(carol))
 83	grc20faucet.Claim(cross(cur))
 84
 85	// Until the holder names this realm on the underlying token, this realm has
 86	// no authority over their balance whatsoever. That is the GRC20 half of the
 87	// trade, and it is the half a native coin does not have.
 88	uassert.AbortsContains(t, cur, "allowance", func() { Wrap(cross(cur), "nred", 1_000) })
 89	uassert.Equal(t, int64(0), envelope.BalanceOf(carol, Denom("nred")))
 90}
 91
 92func TestWrapEscrowsAndIssues(cur realm, t *testing.T) {
 93	dave := testutils.TestAddress("nf-dave")
 94	testing.SetRealm(testing.NewUserRealm(dave))
 95	grc20faucet.Claim(cross(cur))
 96	grc20faucet.Approve(cross(cur), "RED", Home(), 1_000_000)
 97
 98	denom := Denom("nred")
 99	redBefore := grc20faucet.BalanceOf("RED", dave)
100	escrowBefore, issuedBefore := Solvency("nred")
101
102	got := Wrap(cross(cur), "nred", 400_000)
103
104	uassert.Equal(t, int64(400_000), got)
105	uassert.Equal(t, redBefore-400_000, grc20faucet.BalanceOf("RED", dave), "the GRC20 left the holder")
106	uassert.Equal(t, escrowBefore+400_000, grc20faucet.BalanceOf("RED", Home()), "and landed in escrow")
107	uassert.Equal(t, int64(400_000), envelope.BalanceOf(dave, denom), "the native coin is at the bank, 1:1")
108
109	escrowAfter, issuedAfter := Solvency("nred")
110	uassert.Equal(t, escrowBefore+400_000, escrowAfter)
111	uassert.Equal(t, issuedBefore+400_000, issuedAfter)
112	uassert.True(t, IsSolvent("nred"), "every coin issued is backed")
113
114	uassert.AbortsContains(t, cur, "positive amount", func() { Wrap(cross(cur), "nred", 0) })
115	uassert.AbortsContains(t, cur, "does not issue", func() { Wrap(cross(cur), "ghost", 1) })
116}
117
118func TestUnwrapDestroysTheCoinAndReleasesTheBacking(cur realm, t *testing.T) {
119	erin := testutils.TestAddress("nf-erin")
120	denom := Denom("nred")
121
122	testing.SetRealm(testing.NewUserRealm(erin))
123	grc20faucet.Claim(cross(cur))
124	grc20faucet.Approve(cross(cur), "RED", Home(), 1_000_000)
125	Wrap(cross(cur), "nred", 300_000)
126
127	// On chain the -send envelope moves the coins to this realm's address before
128	// Unwrap runs. The harness has no bank-send primitive, so the credit is
129	// issued here; SetOriginSend is the half the realm actually reads.
130	sent := chain.NewCoins(chain.NewCoin(denom, 120_000))
131	testing.IssueCoins(Home(), sent)
132	testing.SetOriginSend(sent)
133
134	redBefore := grc20faucet.BalanceOf("RED", erin)
135	escrowBefore := grc20faucet.BalanceOf("RED", Home())
136	_, issuedBefore := Solvency("nred")
137
138	got := Unwrap(cross(cur), "nred")
139
140	uassert.Equal(t, int64(120_000), got)
141	uassert.Equal(t, redBefore+120_000, grc20faucet.BalanceOf("RED", erin), "the backing came back")
142	uassert.Equal(t, escrowBefore-120_000, grc20faucet.BalanceOf("RED", Home()), "escrow shrank by the same")
143	_, issuedAfter := Solvency("nred")
144	uassert.Equal(t, issuedBefore-120_000, issuedAfter,
145		"the coins were destroyed, not moved: total supply fell")
146
147	testing.SetOriginSend(nil)
148	uassert.AbortsContains(t, cur, "must be paid", func() { Unwrap(cross(cur), "nred") })
149}
150
151func TestRender(cur realm, t *testing.T) {
152	index := Render("")
153	uassert.True(t, strings.Contains(index, "# nativeify"), index)
154	uassert.True(t, strings.Contains(index, Home().String()), "the escrow address is on the page")
155	uassert.True(t, strings.Contains(index, "| [nred](/r/nym-moultest123/x/nativeify/v0:nred) |"),
156		"the pair is listed:\n"+index)
157
158	one := Render("nred")
159	uassert.True(t, strings.Contains(one, "/gno.land/r/nym-moultest123/x/nativeify/v0:nred"), one)
160	uassert.True(t, strings.Contains(one, "a display hint only"), one)
161
162	uassert.True(t, strings.Contains(Render("ghost"), "404"), "an unknown base renders a 404")
163}
164
165// attemptPull is the thing a GRC20 facade over a native coin would have to do,
166// written out so it can be shown failing. It is in the test file, so it is not
167// part of the deployed package.
168func attemptPull(cur realm, from address, denom string, amount int64) {
169	banker.NewBanker(banker.BankerTypeRealmIssue, cur).
170		SendCoins(from, Home(), chain.NewCoins(chain.NewCoin(denom, amount)))
171}
172
173func TestARealmCannotPullANativeCoin(cur realm, t *testing.T) {
174	// The claim the package doc rests on, pinned rather than asserted: this
175	// realm holds the strongest banker the chain offers, the one that can mint
176	// and destroy this denom at will, and it still cannot MOVE a unit it does
177	// not already hold. There is no allowance to grant and no TransferFrom to
178	// call, which is why a grc20.Teller over a native coin cannot be written and
179	// why wugnot wraps in the direction it does.
180	frank := testutils.TestAddress("nf-frank")
181	denom := Denom("nred")
182	testing.IssueCoins(frank, chain.NewCoins(chain.NewCoin(denom, 1_000)))
183
184	msg := pullError(cur, frank, denom, 1_000)
185	uassert.True(t, strings.Contains(msg, "can only send coins from realm that created banker"),
186		"got: "+msg)
187	uassert.Equal(t, int64(1_000), envelope.BalanceOf(frank, denom), "and nothing moved")
188}
189
190// pullError runs attemptPull and returns the refusal as a string.
191//
192// uassert.PanicsContains cannot read this one. banker.SendCoins builds its
193// message as `"..." + b.pkgAddr + "..."`, and in gno a string concatenated with
194// an `address` has type `address`, so the panic value is an address rather than
195// a string and the helper reports "recover: unsupported type". Measured
196// 2026-09-28 against gno master; the message itself is correct, only its type is
197// surprising.
198func pullError(cur realm, from address, denom string, amount int64) (msg string) {
199	defer func() {
200		switch r := recover().(type) {
201		case nil:
202			msg = "it did not fail at all"
203		case address:
204			msg = string(r)
205		case string:
206			msg = r
207		case error:
208			msg = r.Error()
209		default:
210			msg = "panicked with an unexpected type"
211		}
212	}()
213	attemptPull(cur, from, denom, amount)
214	return ""
215}
216
217// ---------------------------------------------------------------------------
218// p/moul/x/envelope's Forward is exercised here rather than in its own package:
219// minting a BankerTypeOriginSend banker calls rlm.Previous(), and a p/ package's
220// test has no realm frame to walk, so it dies with "frame not found: cannot seek
221// beyond origin caller override". A p/ helper that needs a realm handle can only
222// be tested from a realm.
223
224func forwardDirect(cur realm, to address, coins chain.Coins) {
225	envelope.Forward(0, cur, to, coins)
226}
227
228func forwardOneDeeper(cur realm, to address, coins chain.Coins) {
229	forwardDirect(cur, to, coins)
230}
231
232func TestForwardIsBoundedByTheEnvelope(cur realm, t *testing.T) {
233	to := testutils.TestAddress("nf-forward-to")
234	denom := Denom("nred")
235
236	// The realm holds 5,000 of its own, and the envelope carried 250. A
237	// RealmSend banker could spend all 5,250; this one may spend 250.
238	testing.IssueCoins(Home(), chain.NewCoins(chain.NewCoin(denom, 5_000)))
239	sent := chain.NewCoins(chain.NewCoin(denom, 250))
240	testing.IssueCoins(Home(), sent)
241	testing.SetOriginSend(sent)
242
243	heldBefore := envelope.BalanceOf(Home(), denom)
244	forwardDirect(cur, to, sent)
245
246	uassert.Equal(t, int64(250), envelope.BalanceOf(to, denom))
247	uassert.Equal(t, heldBefore-250, envelope.BalanceOf(Home(), denom),
248		"exactly the envelope left, and none of the realm's own balance")
249
250	// Spending past the envelope is refused by the VM, not by any check here.
251	uassert.PanicsContains(t, cur, "limit", func() {
252		forwardDirect(cur, to, chain.NewCoins(chain.NewCoin(denom, 5_000)))
253	})
254	testing.SetOriginSend(nil)
255}
256
257func TestForwardFromOneFrameDeeper(cur realm, t *testing.T) {
258	// The frame-depth rule, measured rather than assumed: NewBanker refuses
259	// BankerTypeOriginSend unless rlm.Previous().IsUserCall(), and IsUserCall
260	// counts at most two call-boundary frames. Whatever this run reports is what
261	// the package doc says.
262	to := testutils.TestAddress("nf-deep-to")
263	denom := Denom("nred")
264	sent := chain.NewCoins(chain.NewCoin(denom, 90))
265	testing.IssueCoins(Home(), sent)
266	testing.SetOriginSend(sent)
267
268	forwardOneDeeper(cur, to, sent)
269	uassert.Equal(t, int64(90), envelope.BalanceOf(to, denom),
270		"one extra frame between the entry point and Forward")
271	testing.SetOriginSend(nil)
272}