Transaction

575AC9FEF073E5…73B04B5174F9

Block 424,888 · index 0 · indexed

Summary

Hash
575AC9FEF073E502E380058A4EB9D5546AF3FB0D45B4990E3E9673B04B5174F9
Block
424,888
Size
10543 bytes
Gas used
16,895,097 / 38,514,800
Fee
115544ugnot
Status
success

Messages

#1AddPackagegno.land/r/moul/x/pilotdemo/v09 arguments
Attached funds
5000000ugnot

Arguments · 9

  1. #1pilotdemo
  2. #2README.md
  3. #3# `gno.land/r/moul/x/pilotdemo/v0` A power for a realm-driven account: a payout module installed into [`r/moul/pilot`](../../pilot) after that account was already deployed. Demo of [`p/moul/pilot`](../../../p/moul/pilot). It shows both delegation modes in one realm, which is the point of the pair: - `Pay` goes through the account's revocable purse. `Revoke` stops it on the next call even though this realm still holds the purse object. - Under an identity grant it acts as `gno.land/r/moul/pilot/v0#payout` toward another realm, and spends the sub-treasury through a banker it mints and **keeps**. That is deliberate: it is what makes the test `TestIdentityGrantOutlivesRevoke` pass, and what an identity grant costs. `InstallInto` takes the account handle as a value rather than importing one account, so a module is not bound to a single instance. gno has no dynamic call, so a `gnokey maketx run` script is what passes the handle; `MsgCall` cannot. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/x/pilotdemo/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/x/pilotdemo/v0/deps.png) > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
  4. #4gnomod.toml
  5. #5module = "gno.land/r/moul/x/pilotdemo/v0" gno = "0.9" # public: a pilot account persists the module object in its roster, and a # value of a type defined in a private realm cannot be persisted by anyone # else. Every pilot module has to be public for the same reason.
  6. #6pilotdemo.gno
  7. #7// Package pilotdemo is a power for a realm-driven account: a payout module // installed into gno.land/p/moul/pilot/v0 after the account was deployed. // // It shows both delegation modes side by side: // // - Pay goes through the account's revocable [pilot.Purse]. Revoking the // module stops it on the next call, even though this realm still holds // the purse object. // - Act is only reachable under an identity grant: the account lends its // sub-identity, this realm acts as "<account>#<subpath>" toward another // realm, and spends the sub-treasury through a banker it mints. That // banker is KEPT on purpose, which is what makes an identity grant // permanent: revoking stops Exec, not this. // // Demo of gno.land/p/moul/pilot/v0. Account instance: r/moul/pilot. package pilotdemo import ( "chain" "chain/banker" "gno.land/p/moul/pilot/v0" "gno.land/p/nt/ufmt/v0" account "gno.land/r/moul/pilot/v0" ) type payout struct { purse *pilot.Purse kept banker.Banker // minted from a lent identity, and retained sub address dest address paid int64 } func (m *payout) Name() string { return "payout" } // Run is what the account calls. rlm is the account's sub-identity token // under an identity grant, and its plain cur otherwise. func (m *payout) Run(_ int, rlm realm, args string) string { if !rlm.IsCurrent() { panic("pilotdemo: stale realm value") } if rlm.Subpath() == "" { // purse-only grant: no identity to act under m.purse.Pay(m.dest, 100) m.paid += 100 return ufmt.Sprintf("paid 100ugnot from the main treasury, %d left", m.purse.Left()) } // identity grant: act as the account toward another realm... seen := Echo(cross(rlm)) // ...and spend the sub-treasury it owns. m.sub = rlm.Address() m.kept = banker.NewBanker(banker.BankerTypeRealmSend, rlm) m.kept.SendCoins(m.sub, m.dest, chain.NewCoins(chain.NewCoin("ugnot", 100))) m.paid += 100 return "acted as " + seen } var self = &payout{} // Install wires this module into moul's account. func Install(cur realm, dest address) { InstallInto(cur, account.Handle(), dest) } // InstallInto wires it into ANY account: a module is not bound to one // instance. gno has no dynamic call, so the handle has to be passed as a // value, which a `gnokey maketx run` script can do and a MsgCall cannot. func InstallInto(cur realm, acct *pilot.Account, dest address) { self.dest = dest acct.Register(0, cur, self) self.purse = acct.PurseFor(0, cur) } // Echo reports who called it. A crossing call shifts the previous-realm // stack even into the same realm, so this is what the module's borrowed // identity looks like from the outside. func Echo(cur realm) string { return cur.Previous().PkgPath() } // Payout spends the sub-treasury with the retained banker, reaching the bank // without re-entering the account. This is the demonstration that an // identity grant cannot be revoked: it keeps working after Revoke. func Payout(cur realm, to address, amount int64) { if self.kept == nil { panic("pilotdemo: no identity was ever granted") } self.kept.SendCoins(self.sub, to, chain.NewCoins(chain.NewCoin("ugnot", amount))) } // Paid is what this module has moved in total. func Paid() int64 { return self.paid } func Render(path string) string { if self.purse == nil { return "# pilotdemo\n\nNot installed.\n" } return ufmt.Sprintf("# pilotdemo\n\n| | |\n|---|---|\n| destination | %s |\n| purse left | %d ugnot |\n", self.dest.String(), self.purse.Left()) }
  8. #8pilotdemo_test.gno
  9. #9package pilotdemo import ( "chain" "chain/banker" "testing" "gno.land/p/nt/testutils/v0" "gno.land/p/nt/uassert/v0" account "gno.land/r/moul/pilot/v0" ) const modPath = "gno.land/r/moul/x/pilotdemo/v0" var ( // r/moul/pilot pins Claim to this address, so the tests have to use it owner = address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") dest = testutils.TestAddress("dest") claimed bool ) // claim is safe to call from every test: realm state carries over between // test functions, so the account is claimed exactly once per run. func claim(cur realm) { if claimed { return } claimed = true account.Claim(cross(cur)) } func balance(addr address) int64 { return banker.NewReadonlyBanker().GetCoin(addr, "ugnot") } func TestPurseGrantIsRevocable(cur realm, t *testing.T) { testing.SetRealm(testing.NewUserRealm(owner)) claim(cur) treasury := account.Address() testing.IssueCoins(treasury, chain.NewCoins(chain.NewCoin("ugnot", 10_000))) testing.SetRealm(testing.NewUserRealm(owner)) account.Approve(cross(cur), modPath, "payout", false, 250) // the module installs itself; the account reads its path from the runtime Install(cross(cur), dest) testing.SetRealm(testing.NewUserRealm(owner)) uassert.Equal(t, "paid 100ugnot from the main treasury, 150 left", account.Exec(cross(cur), modPath, "")) uassert.Equal(t, int64(100), balance(dest)) uassert.Equal(t, int64(9_900), balance(treasury)) // the owner revokes. the module still holds the purse object. testing.SetRealm(testing.NewUserRealm(owner)) account.Revoke(cross(cur), modPath) uassert.Equal(t, int64(0), self.purse.Left()) // both doors are shut: through the account... testing.SetRealm(testing.NewUserRealm(owner)) uassert.AbortsContains(t, cur, "revoked", func() { account.Exec(cross(cur), modPath, "") }) // ...and through the retained purse, which is the one that matters. uassert.AbortsContains(t, cur, "revoked", func() { self.purse.Pay(dest, 10) }) uassert.Equal(t, int64(100), balance(dest)) } func TestIdentityGrantActsAsTheAccount(cur realm, t *testing.T) { testing.SetRealm(testing.NewUserRealm(owner)) claim(cur) testing.SetRealm(testing.NewUserRealm(owner)) account.Approve(cross(cur), modPath, "payout", true, 250) Install(cross(cur), dest) sub := account.SubAddress(modPath) testing.IssueCoins(account.Address(), chain.NewCoins(chain.NewCoin("ugnot", 10_000))) testing.SetRealm(testing.NewUserRealm(owner)) account.Fund(cross(cur), modPath, 1_000) uassert.Equal(t, int64(1_000), balance(sub)) testing.SetRealm(testing.NewUserRealm(owner)) got := account.Exec(cross(cur), modPath, "") // the module acted as the ACCOUNT's sub-identity, not as itself uassert.Equal(t, "acted as gno.land/r/moul/pilot/v0#payout", got) uassert.Equal(t, int64(900), balance(sub)) uassert.Equal(t, int64(100), balance(dest)) } func TestIdentityGrantOutlivesRevoke(cur realm, t *testing.T) { testing.SetRealm(testing.NewUserRealm(owner)) claim(cur) testing.SetRealm(testing.NewUserRealm(owner)) account.Approve(cross(cur), modPath, "payout", true, 250) Install(cross(cur), dest) sub := account.SubAddress(modPath) testing.IssueCoins(account.Address(), chain.NewCoins(chain.NewCoin("ugnot", 10_000))) testing.SetRealm(testing.NewUserRealm(owner)) account.Fund(cross(cur), modPath, 1_000) testing.SetRealm(testing.NewUserRealm(owner)) account.Exec(cross(cur), modPath, "") // mints and keeps a banker on the sub-address uassert.Equal(t, int64(900), balance(sub)) // revoke shuts the account door testing.SetRealm(testing.NewUserRealm(owner)) account.Revoke(cross(cur), modPath) uassert.AbortsContains(t, cur, "revoked", func() { account.Exec(cross(cur), modPath, "") }) // and the retained banker keeps spending anyway. This is the documented // cost of an identity grant: revoking is not taking it back. testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("anyone"))) Payout(cross(cur), dest, 900) uassert.Equal(t, int64(0), balance(sub)) } func TestRender(cur realm, t *testing.T) { testing.SetRealm(testing.NewUserRealm(owner)) claim(cur) // a fresh approval is a fresh allowance, so this reads the same whatever // ran before it testing.SetRealm(testing.NewUserRealm(owner)) account.Approve(cross(cur), modPath, "payout", false, 1_000) Install(cross(cur), dest) testing.IssueCoins(account.Address(), chain.NewCoins(chain.NewCoin("ugnot", 10_000))) testing.SetRealm(testing.NewUserRealm(owner)) account.Exec(cross(cur), modPath, "") uassert.Equal(t, "# pilotdemo\n\n| | |\n|---|---|\n| destination | "+ dest.String()+" |\n| purse left | 900 ugnot |\n", Render("")) }

Result log

msg:0,success:true,log:,events:[]

← Back to block 424,888