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}