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

bond.gno

6.52 Kb · 249 lines
  1// Package bond is the create-bond policy for gnomemepad — separate from pad
  2// so promo schedules can change without redeploying the pad realm.
  3//
  4// Pad calls CurrentBondUgnot() at Create time. This package owns:
  5//   - normal bond (default 2 GNOT)
  6//   - promo bond (default ~$20 in ugnot at a fixed GNOT rate, admin-adjustable)
  7//   - 10-day window OR admin EndPromo → back to normal
  8//
  9// USD is not on-chain: promo amount is ugnot. Admin sets PromoBondUgnot to
 10// approximate $20 (e.g. at $1/GNOT → 20e6 ugnot; at $0.5/GNOT → 40e6).
 11package bond
 12
 13import (
 14	"chain"
 15	"strconv"
 16	"time"
 17)
 18
 19const (
 20	DenomUgnot = "ugnot"
 21
 22	// Defaults (ugnot, 1 GNOT = 1e6 ugnot)
 23	// Pearl / current policy: 100 GNOT create bond (admin can SetNormalBond).
 24	DefaultNormalBondUgnot int64 = 100_000_000 // 100 GNOT
 25	DefaultPromoBondUgnot  int64 = 100_000_000 // same unless StartPromo changes it
 26	DefaultPromoDuration   int64 = 10 * 24 * 60 * 60 // 10 days (seconds)
 27
 28	// Status for UI
 29	StatusNormal = 0
 30	StatusPromo  = 1
 31)
 32
 33var (
 34	inited bool
 35	admin  address
 36
 37	normalBondUgnot int64
 38	promoBondUgnot  int64
 39	// promo: active while time.Now().Unix() < promoEndsAt and !promoEnded
 40	promoEndsAt int64
 41	promoEnded  bool // admin force-end (or auto after window)
 42	promoStartedAt int64
 43)
 44
 45// Init sets admin (deploy EOA) and default bond amounts. Call once.
 46func Init(cur realm) {
 47	if inited {
 48		panic("bond: already initialized")
 49	}
 50	if !cur.Previous().IsUserCall() {
 51		panic("bond: EOA only")
 52	}
 53	admin = cur.Previous().Address()
 54	normalBondUgnot = DefaultNormalBondUgnot
 55	promoBondUgnot = DefaultPromoBondUgnot
 56	promoEndsAt = 0
 57	promoEnded = true
 58	promoStartedAt = 0
 59	inited = true
 60	chain.Emit("Init", "admin", admin.String())
 61}
 62
 63func requireInit() {
 64	if !inited {
 65		panic("bond: call Init first")
 66	}
 67}
 68
 69func requireAdmin(cur realm) {
 70	requireInit()
 71	if !cur.Previous().IsUserCall() {
 72		panic("bond: EOA only")
 73	}
 74	if cur.Previous().Address() != admin {
 75		panic("bond: not admin")
 76	}
 77}
 78
 79// Admin returns the admin (deploy) address.
 80func Admin() string {
 81	requireInit()
 82	return admin.String()
 83}
 84
 85// TransferAdmin rotates admin (current admin only).
 86func TransferAdmin(cur realm, newAdmin address) {
 87	requireAdmin(cur)
 88	if !newAdmin.IsValid() {
 89		panic("bond: invalid address")
 90	}
 91	old := admin
 92	admin = newAdmin
 93	chain.Emit("TransferAdmin", "from", old.String(), "to", newAdmin.String())
 94}
 95
 96// IsPromoActive reports whether the promo window is currently in force.
 97func IsPromoActive() bool {
 98	if !inited || promoEnded || promoEndsAt <= 0 {
 99		return false
100	}
101	now := time.Now().Unix()
102	if now >= promoEndsAt {
103		return false
104	}
105	return true
106}
107
108// CurrentBondUgnot is the create bond pad must require (cross-safe read).
109// Pad: if sent < CurrentBondUgnot() → panic underpaid.
110func CurrentBondUgnot() int64 {
111	requireInit()
112	if IsPromoActive() {
113		return promoBondUgnot
114	}
115	return normalBondUgnot
116}
117
118// NormalBondUgnot returns the post-promo / default bond.
119func NormalBondUgnot() int64 {
120	requireInit()
121	return normalBondUgnot
122}
123
124// PromoBondUgnot returns the promo-period bond amount (may be inactive).
125func PromoBondUgnot() int64 {
126	requireInit()
127	return promoBondUgnot
128}
129
130// PromoEndsAt returns unix end of promo (0 if never started).
131func PromoEndsAt() int64 {
132	requireInit()
133	return promoEndsAt
134}
135
136// PromoSecondsLeft returns remaining promo seconds (0 if inactive).
137func PromoSecondsLeft() int64 {
138	if !IsPromoActive() {
139		return 0
140	}
141	left := promoEndsAt - time.Now().Unix()
142	if left < 0 {
143		return 0
144	}
145	return left
146}
147
148// Status: 0=normal 1=promo active.
149func Status() int {
150	if IsPromoActive() {
151		return StatusPromo
152	}
153	return StatusNormal
154}
155
156// BondInfo: status|currentUgnot|normalUgnot|promoUgnot|endsAt|secondsLeft|admin
157func BondInfo() string {
158	requireInit()
159	st := Status()
160	return strconv.Itoa(st) + "|" +
161		strconv.FormatInt(CurrentBondUgnot(), 10) + "|" +
162		strconv.FormatInt(normalBondUgnot, 10) + "|" +
163		strconv.FormatInt(promoBondUgnot, 10) + "|" +
164		strconv.FormatInt(promoEndsAt, 10) + "|" +
165		strconv.FormatInt(PromoSecondsLeft(), 10) + "|" +
166		admin.String()
167}
168
169// StartPromo begins a promo window from now.
170// promoUgnot: bond during promo (e.g. 20e6 ≈ $20 at $1/GNOT). 0 → keep current promoBondUgnot.
171// durationSec: window length. 0 → DefaultPromoDuration (10 days).
172func StartPromo(cur realm, promoUgnot, durationSec int64) {
173	requireAdmin(cur)
174	if promoUgnot < 0 {
175		panic("bond: promoUgnot must be non-negative")
176	}
177	if promoUgnot > 0 {
178		promoBondUgnot = promoUgnot
179	}
180	if promoBondUgnot <= 0 {
181		panic("bond: set promo bond amount first")
182	}
183	if durationSec <= 0 {
184		durationSec = DefaultPromoDuration
185	}
186	now := time.Now().Unix()
187	promoStartedAt = now
188	promoEndsAt = now + durationSec
189	promoEnded = false
190	chain.Emit("StartPromo",
191		"promoUgnot", strconv.FormatInt(promoBondUgnot, 10),
192		"endsAt", strconv.FormatInt(promoEndsAt, 10),
193		"durationSec", strconv.FormatInt(durationSec, 10),
194	)
195}
196
197// EndPromo forces normal bond immediately (deploy admin).
198func EndPromo(cur realm) {
199	requireAdmin(cur)
200	promoEnded = true
201	// keep promoEndsAt for history
202	chain.Emit("EndPromo",
203		"at", strconv.FormatInt(time.Now().Unix(), 10),
204		"normalUgnot", strconv.FormatInt(normalBondUgnot, 10),
205	)
206}
207
208// SetNormalBond updates the post-promo bond (default 2 GNOT).
209func SetNormalBond(cur realm, ugnot int64) {
210	requireAdmin(cur)
211	if ugnot <= 0 {
212		panic("bond: normal bond must be positive")
213	}
214	normalBondUgnot = ugnot
215	chain.Emit("SetNormalBond", "ugnot", strconv.FormatInt(ugnot, 10))
216}
217
218// SetPromoBond updates promo amount without restarting the clock.
219// Use to re-target ~$20 when GNOT price moves during the window.
220func SetPromoBond(cur realm, ugnot int64) {
221	requireAdmin(cur)
222	if ugnot <= 0 {
223		panic("bond: promo bond must be positive")
224	}
225	promoBondUgnot = ugnot
226	chain.Emit("SetPromoBond", "ugnot", strconv.FormatInt(ugnot, 10))
227}
228
229// ExtendPromo adds seconds to the current end (or starts from now if inactive).
230func ExtendPromo(cur realm, extraSec int64) {
231	requireAdmin(cur)
232	if extraSec <= 0 {
233		panic("bond: extraSec must be positive")
234	}
235	now := time.Now().Unix()
236	if !IsPromoActive() {
237		// restart promo with current promoBondUgnot
238		promoStartedAt = now
239		promoEndsAt = now + extraSec
240		promoEnded = false
241	} else {
242		promoEndsAt += extraSec
243		promoEnded = false
244	}
245	chain.Emit("ExtendPromo",
246		"endsAt", strconv.FormatInt(promoEndsAt, 10),
247		"extraSec", strconv.FormatInt(extraSec, 10),
248	)
249}