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