Search Apps Documentation Source Content File Folder Download Copy Actions Download

migration.gno

3.58 Kb · 88 lines
 1// Package memba_points_migration_v1 is a one-time, owner-gated importer of legacy backend XP into the
 2// soulbound memba_points_v1 ledger. It holds NO funds and mints NO points itself — it calls the audited
 3// awarder path (memba_points_v1.Award) so every migrated grant lands as a normal PointsAwarded event
 4// (source = this realm, reason = "xp_migration"). Idempotent per-address + permanently finalizable.
 5//
 6// The parsing / validation / dedup logic lives in the pure, unit-tested
 7// p/samcrew/memba_points_migration_core_v1 package; this realm is the thin state + effect shell
 8// (owner gate, migrated set, finalize latch, the Award loop). It can't be unit-tested under bare
 9// `gno test` because it imports the unpublished points_v1 — its logic is covered by the core tests
10// plus a test13 ceremony dry-run.
11//
12// Rollout (owner ceremony, after points_v1 is deployed): points_v1.AddAwarder(this realm) →
13// MigrateBatch × N → FinalizeMigration → points_v1.RemoveAwarder(this realm). See README.md.
14package memba_points_migration_v1
15
16import (
17	"chain"
18
19	"gno.land/p/nt/avl/v0"
20	"gno.land/p/nt/ufmt/v0"
21	core "gno.land/p/samcrew/memba_points_migration_core_v1"
22	points "gno.land/r/samcrew/memba_points_v1"
23)
24
25// AdminAddress inits owner — the samcrew-core deploy multisig (same as memba_points_v1). Hand it to the
26// memba_dao executor post-deploy via a 2-step transfer if desired.
27const AdminAddress = "g1x7k4628w93a7wzdhqc06atzx0v50rnshweuxu0"
28
29const (
30	// MaxBatch bounds entries per MigrateBatch tx (gas).
31	MaxBatch = 100
32	// reasonXPMigration tags every migrated grant in the points_v1 audit trail.
33	reasonXPMigration = "xp_migration"
34)
35
36var (
37	owner     address
38	migrated  = avl.NewTree() // addr string -> true  (idempotency ledger)
39	finalized bool            // once true, MigrateBatch refuses forever
40)
41
42func init() { owner = address(AdminAddress) }
43
44// assertOwner uses the interrealm-v2 sanctioned primitives (statically scoped to this crossing frame,
45// cannot be spoofed) — matching the hardened memba_points_v1.
46func assertOwner(cur realm) {
47	if !cur.IsCurrent() {
48		panic("spoofed realm")
49	}
50	if cur.Previous().Address() != owner {
51		panic("unauthorized: owner only")
52	}
53}
54
55// MigrateBatch imports a batch of legacy XP as points. Owner only. `entries` is
56// "addr:amount,addr:amount" (≤ MaxBatch). Malformed input fails the whole tx (fail-fast); an
57// already-migrated address is skipped; the call is refused once finalized. Each fresh entry is awarded
58// through the audited memba_points_v1 awarder path.
59func MigrateBatch(cur realm, entries string) {
60	assertOwner(cur)
61	plan := core.PlanBatch(entries, points.MaxAward, MaxBatch, finalized, func(a string) bool {
62		return migrated.Has(a)
63	})
64	for _, e := range plan {
65		points.Award(cross(cur), address(e.Addr), e.Amount, reasonXPMigration)
66		migrated.Set(e.Addr, true)
67		chain.Emit("XPMigrated", "addr", e.Addr, "amount", ufmt.Sprintf("%d", e.Amount))
68	}
69}
70
71// FinalizeMigration permanently closes the migration — no further MigrateBatch. Owner only, irreversible.
72func FinalizeMigration(cur realm) {
73	assertOwner(cur)
74	finalized = true
75	chain.Emit("MigrationFinalized")
76}
77
78// IsMigrated reports whether an address has already been imported.
79func IsMigrated(addr string) bool { return migrated.Has(addr) }
80
81// IsFinalized reports whether the migration is permanently closed.
82func IsFinalized() bool { return finalized }
83
84// Render — minimal gnoweb status (owner is a fixed const-derived address; no untrusted input).
85func Render(_ string) string {
86	return ufmt.Sprintf("# Memba Points — XP Migration\n\n- Finalized: **%t**\n- Owner: `%s`\n",
87		finalized, owner.String())
88}