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

store.gno

17.74 Kb · 677 lines
  1package staker
  2
  3import (
  4	"errors"
  5	"gno.land/p/gnoswap/store"
  6	bptree "gno.land/p/nt/bptree/v0"
  7	ufmt "gno.land/p/nt/ufmt/v0"
  8)
  9
 10type StoreKey string
 11
 12func (s StoreKey) String() string {
 13	return string(s)
 14}
 15
 16const (
 17	StoreKeyDepositGnsAmount                 StoreKey = "depositGnsAmount"
 18	StoreKeyMinimumRewardAmount              StoreKey = "minimumRewardAmount"
 19	StoreKeyDeposits                         StoreKey = "deposits"
 20	StoreKeyExternalIncentives               StoreKey = "externalIncentives"
 21	StoreKeyTotalEmissionSent                StoreKey = "totalEmissionSent"
 22	StoreKeyAllowedTokens                    StoreKey = "allowedTokens"
 23	StoreKeyIncentiveCounter                 StoreKey = "incentiveCounter"
 24	StoreKeyTokenSpecificMinimumRewards      StoreKey = "tokenSpecificMinimumRewards"
 25	StoreKeyUnstakingFee                     StoreKey = "unstakingFee"
 26	StoreKeyPendingProtocolFees              StoreKey = "pendingProtocolFees"
 27	StoreKeyPools                            StoreKey = "pools"
 28	StoreKeyPoolTierMemberships              StoreKey = "poolTierMemberships"
 29	StoreKeyPoolTierRatio                    StoreKey = "poolTierRatio"
 30	StoreKeyPoolTierCounts                   StoreKey = "poolTierCounts"
 31	StoreKeyPoolTierLastRewardCacheTimestamp StoreKey = "poolTierLastRewardCacheTimestamp"
 32	StoreKeyPoolTierCurrentEmission          StoreKey = "poolTierCurrentEmission"
 33	StoreKeyPoolTierGetEmission              StoreKey = "poolTierGetEmission"
 34	StoreKeyPoolTierGetHalvingBlocksInRange  StoreKey = "poolTierGetHalvingBlocksInRange"
 35	StoreKeyWarmupTemplate                   StoreKey = "warmupTemplate"
 36	StoreKeyCurrentSwapBatch                 StoreKey = "currentSwapBatch"
 37)
 38
 39type stakerStore struct {
 40	kvStore store.KVStore
 41}
 42
 43// DepositGnsAmount
 44func (s *stakerStore) HasDepositGnsAmountStoreKey() bool {
 45	return s.kvStore.Has(StoreKeyDepositGnsAmount.String())
 46}
 47
 48func (s *stakerStore) GetDepositGnsAmount() int64 {
 49	result, err := s.kvStore.Get(StoreKeyDepositGnsAmount.String())
 50	if err != nil {
 51		panic(err)
 52	}
 53
 54	amount, ok := result.(int64)
 55	if !ok {
 56		panic(ufmt.Sprintf("failed to cast result to int64: %T", result))
 57	}
 58
 59	return amount
 60}
 61
 62func (s *stakerStore) SetDepositGnsAmount(_ int, rlm realm, amount int64) error {
 63	if !rlm.IsCurrent() {
 64		return errors.New(ErrSpoofedRealm)
 65	}
 66
 67	return s.kvStore.Set(0, rlm, StoreKeyDepositGnsAmount.String(), amount)
 68}
 69
 70// MinimumRewardAmount
 71func (s *stakerStore) HasMinimumRewardAmountStoreKey() bool {
 72	return s.kvStore.Has(StoreKeyMinimumRewardAmount.String())
 73}
 74
 75func (s *stakerStore) GetMinimumRewardAmount() int64 {
 76	result, err := s.kvStore.Get(StoreKeyMinimumRewardAmount.String())
 77	if err != nil {
 78		panic(err)
 79	}
 80
 81	amount, ok := result.(int64)
 82	if !ok {
 83		panic(ufmt.Sprintf("failed to cast result to int64: %T", result))
 84	}
 85
 86	return amount
 87}
 88
 89func (s *stakerStore) SetMinimumRewardAmount(_ int, rlm realm, amount int64) error {
 90	if !rlm.IsCurrent() {
 91		return errors.New(ErrSpoofedRealm)
 92	}
 93
 94	return s.kvStore.Set(0, rlm, StoreKeyMinimumRewardAmount.String(), amount)
 95}
 96
 97// Deposits
 98func (s *stakerStore) HasDepositsStoreKey() bool {
 99	return s.kvStore.Has(StoreKeyDeposits.String())
100}
101
102func (s *stakerStore) GetDeposits() *bptree.BPTree {
103	result, err := s.kvStore.Get(StoreKeyDeposits.String())
104	if err != nil {
105		panic(err)
106	}
107
108	deposits, ok := result.(*bptree.BPTree)
109	if !ok {
110		panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
111	}
112
113	return deposits
114}
115
116func (s *stakerStore) SetDeposits(_ int, rlm realm, deposits *bptree.BPTree) error {
117	if !rlm.IsCurrent() {
118		return errors.New(ErrSpoofedRealm)
119	}
120
121	return s.kvStore.Set(0, rlm, StoreKeyDeposits.String(), deposits)
122}
123
124// ExternalIncentives
125func (s *stakerStore) HasExternalIncentivesStoreKey() bool {
126	return s.kvStore.Has(StoreKeyExternalIncentives.String())
127}
128
129func (s *stakerStore) GetExternalIncentives() *bptree.BPTree {
130	result, err := s.kvStore.Get(StoreKeyExternalIncentives.String())
131	if err != nil {
132		panic(err)
133	}
134
135	incentives, ok := result.(*bptree.BPTree)
136	if !ok {
137		panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
138	}
139
140	return incentives
141}
142
143func (s *stakerStore) SetExternalIncentives(_ int, rlm realm, incentives *bptree.BPTree) error {
144	if !rlm.IsCurrent() {
145		return errors.New(ErrSpoofedRealm)
146	}
147
148	return s.kvStore.Set(0, rlm, StoreKeyExternalIncentives.String(), incentives)
149}
150
151// TotalEmissionSent
152func (s *stakerStore) HasTotalEmissionSentStoreKey() bool {
153	return s.kvStore.Has(StoreKeyTotalEmissionSent.String())
154}
155
156func (s *stakerStore) GetTotalEmissionSent() int64 {
157	result, err := s.kvStore.Get(StoreKeyTotalEmissionSent.String())
158	if err != nil {
159		panic(err)
160	}
161
162	amount, ok := result.(int64)
163	if !ok {
164		panic(ufmt.Sprintf("failed to cast result to int64: %T", result))
165	}
166
167	return amount
168}
169
170func (s *stakerStore) SetTotalEmissionSent(_ int, rlm realm, amount int64) error {
171	if !rlm.IsCurrent() {
172		return errors.New(ErrSpoofedRealm)
173	}
174
175	return s.kvStore.Set(0, rlm, StoreKeyTotalEmissionSent.String(), amount)
176}
177
178// AllowedTokens
179func (s *stakerStore) HasAllowedTokensStoreKey() bool {
180	return s.kvStore.Has(StoreKeyAllowedTokens.String())
181}
182
183func (s *stakerStore) GetAllowedTokens() []string {
184	result, err := s.kvStore.Get(StoreKeyAllowedTokens.String())
185	if err != nil {
186		panic(err)
187	}
188
189	tokens, ok := result.([]string)
190	if !ok {
191		panic(ufmt.Sprintf("failed to cast result to []string: %T", result))
192	}
193
194	return cloneStringSlice(tokens)
195}
196
197func (s *stakerStore) SetAllowedTokens(_ int, rlm realm, tokens []string) error {
198	if !rlm.IsCurrent() {
199		return errors.New(ErrSpoofedRealm)
200	}
201
202	return s.kvStore.Set(0, rlm, StoreKeyAllowedTokens.String(), tokens)
203}
204
205// AddAllowedToken appends tokenPath to the allowed-tokens list when absent.
206// The store-owned slice is cloned before mutation to avoid readonly-taint panics.
207func (s *stakerStore) AddAllowedToken(_ int, rlm realm, tokenPath string) error {
208	if !rlm.IsCurrent() {
209		return errors.New(ErrSpoofedRealm)
210	}
211
212	tokens := s.GetAllowedTokens()
213	if !contains(tokens, tokenPath) {
214		tokens = append(tokens, tokenPath)
215	}
216
217	return s.kvStore.Set(0, rlm, StoreKeyAllowedTokens.String(), tokens)
218}
219
220// RemoveAllowedToken removes tokenPath from the allowed-tokens list when present.
221// The store-owned slice is cloned before mutation to avoid readonly-taint panics.
222func (s *stakerStore) RemoveAllowedToken(_ int, rlm realm, tokenPath string) error {
223	if !rlm.IsCurrent() {
224		return errors.New(ErrSpoofedRealm)
225	}
226
227	tokens := s.GetAllowedTokens()
228	for i, t := range tokens {
229		if t == tokenPath {
230			tokens = append(tokens[:i], tokens[i+1:]...)
231			break
232		}
233	}
234
235	return s.kvStore.Set(0, rlm, StoreKeyAllowedTokens.String(), tokens)
236}
237
238// IncentiveCounter
239func (s *stakerStore) HasIncentiveCounterStoreKey() bool {
240	return s.kvStore.Has(StoreKeyIncentiveCounter.String())
241}
242
243func (s *stakerStore) GetIncentiveCounter() *Counter {
244	result, err := s.kvStore.Get(StoreKeyIncentiveCounter.String())
245	if err != nil {
246		panic(err)
247	}
248
249	counter, ok := result.(*Counter)
250	if !ok {
251		panic(ufmt.Sprintf("failed to cast result to *Counter: %T", result))
252	}
253
254	return counter
255}
256
257func (s *stakerStore) SetIncentiveCounter(_ int, rlm realm, counter *Counter) error {
258	if !rlm.IsCurrent() {
259		return errors.New(ErrSpoofedRealm)
260	}
261
262	return s.kvStore.Set(0, rlm, StoreKeyIncentiveCounter.String(), counter)
263}
264
265func (s *stakerStore) NextIncentiveID(creator address, timestamp int64) string {
266	counter := s.GetIncentiveCounter()
267	return makeIncentiveID(creator, timestamp, counter.Next())
268}
269
270// TokenSpecificMinimumRewards
271func (s *stakerStore) HasTokenSpecificMinimumRewardsStoreKey() bool {
272	return s.kvStore.Has(StoreKeyTokenSpecificMinimumRewards.String())
273}
274
275func (s *stakerStore) GetTokenSpecificMinimumRewards() map[string]int64 {
276	result, err := s.kvStore.Get(StoreKeyTokenSpecificMinimumRewards.String())
277	if err != nil {
278		panic(err)
279	}
280
281	rewards, ok := result.(map[string]int64)
282	if !ok {
283		panic(ufmt.Sprintf("failed to cast result to map[string]int64: %T", result))
284	}
285
286	return rewards
287}
288
289func (s *stakerStore) SetTokenSpecificMinimumRewards(_ int, rlm realm, rewards map[string]int64) error {
290	if !rlm.IsCurrent() {
291		return errors.New(ErrSpoofedRealm)
292	}
293
294	return s.kvStore.Set(0, rlm, StoreKeyTokenSpecificMinimumRewards.String(), rewards)
295}
296
297// SetTokenSpecificMinimumRewardItem sets a single token's minimum reward amount.
298func (s *stakerStore) SetTokenSpecificMinimumRewardItem(_ int, rlm realm, tokenPath string, amount int64) error {
299	if !rlm.IsCurrent() {
300		return errors.New(ErrSpoofedRealm)
301	}
302
303	rewards := s.GetTokenSpecificMinimumRewards()
304
305	owned := make(map[string]int64)
306	for k, v := range rewards {
307		owned[k] = v
308	}
309
310	owned[tokenPath] = amount
311
312	return s.kvStore.Set(0, rlm, StoreKeyTokenSpecificMinimumRewards.String(), owned)
313}
314
315// RemoveTokenSpecificMinimumRewardItem removes a single token's minimum reward entry.
316func (s *stakerStore) RemoveTokenSpecificMinimumRewardItem(_ int, rlm realm, tokenPath string) error {
317	if !rlm.IsCurrent() {
318		return errors.New(ErrSpoofedRealm)
319	}
320
321	rewards := s.GetTokenSpecificMinimumRewards()
322
323	owned := make(map[string]int64)
324	for k, v := range rewards {
325		if k == tokenPath {
326			continue
327		}
328
329		owned[k] = v
330	}
331
332	return s.kvStore.Set(0, rlm, StoreKeyTokenSpecificMinimumRewards.String(), owned)
333}
334
335// UnstakingFee
336func (s *stakerStore) HasUnstakingFeeStoreKey() bool {
337	return s.kvStore.Has(StoreKeyUnstakingFee.String())
338}
339
340func (s *stakerStore) GetUnstakingFee() uint64 {
341	result, err := s.kvStore.Get(StoreKeyUnstakingFee.String())
342	if err != nil {
343		panic(err)
344	}
345
346	fee, ok := result.(uint64)
347	if !ok {
348		panic(ufmt.Sprintf("failed to cast result to uint64: %T", result))
349	}
350
351	return fee
352}
353
354func (s *stakerStore) SetUnstakingFee(_ int, rlm realm, fee uint64) error {
355	if !rlm.IsCurrent() {
356		return errors.New(ErrSpoofedRealm)
357	}
358
359	return s.kvStore.Set(0, rlm, StoreKeyUnstakingFee.String(), fee)
360}
361
362func (s *stakerStore) HasPendingProtocolFeesStoreKey() bool {
363	return s.kvStore.Has(StoreKeyPendingProtocolFees.String())
364}
365
366func (s *stakerStore) GetPendingProtocolFees() map[string]int64 {
367	result, err := s.kvStore.Get(StoreKeyPendingProtocolFees.String())
368	if err != nil {
369		panic(err)
370	}
371
372	fees, ok := result.(map[string]int64)
373	if !ok {
374		panic(ufmt.Sprintf("failed to cast result to map[string]int64: %T", result))
375	}
376
377	return fees
378}
379
380func (s *stakerStore) SetPendingProtocolFees(_ int, rlm realm, fees map[string]int64) error {
381	if !rlm.IsCurrent() {
382		return errors.New(ErrSpoofedRealm)
383	}
384
385	return s.kvStore.Set(0, rlm, StoreKeyPendingProtocolFees.String(), fees)
386}
387
388// Pools
389func (s *stakerStore) HasPoolsStoreKey() bool {
390	return s.kvStore.Has(StoreKeyPools.String())
391}
392
393func (s *stakerStore) GetPools() *bptree.BPTree {
394	result, err := s.kvStore.Get(StoreKeyPools.String())
395	if err != nil {
396		panic(err)
397	}
398
399	pools, ok := result.(*bptree.BPTree)
400	if !ok {
401		panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
402	}
403
404	return pools
405}
406
407func (s *stakerStore) SetPools(_ int, rlm realm, pools *bptree.BPTree) error {
408	if !rlm.IsCurrent() {
409		return errors.New(ErrSpoofedRealm)
410	}
411
412	return s.kvStore.Set(0, rlm, StoreKeyPools.String(), pools)
413}
414
415// PoolTierMemberships
416func (s *stakerStore) HasPoolTierMembershipsStoreKey() bool {
417	return s.kvStore.Has(StoreKeyPoolTierMemberships.String())
418}
419
420func (s *stakerStore) GetPoolTierMemberships() *bptree.BPTree {
421	result, err := s.kvStore.Get(StoreKeyPoolTierMemberships.String())
422	if err != nil {
423		panic(err)
424	}
425
426	memberships, ok := result.(*bptree.BPTree)
427	if !ok {
428		panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
429	}
430
431	return memberships
432}
433
434func (s *stakerStore) SetPoolTierMemberships(_ int, rlm realm, memberships *bptree.BPTree) error {
435	if !rlm.IsCurrent() {
436		return errors.New(ErrSpoofedRealm)
437	}
438
439	return s.kvStore.Set(0, rlm, StoreKeyPoolTierMemberships.String(), memberships)
440}
441
442// PoolTierRatio
443func (s *stakerStore) HasPoolTierRatioStoreKey() bool {
444	return s.kvStore.Has(StoreKeyPoolTierRatio.String())
445}
446
447func (s *stakerStore) GetPoolTierRatio() TierRatio {
448	result, err := s.kvStore.Get(StoreKeyPoolTierRatio.String())
449	if err != nil {
450		panic(err)
451	}
452
453	ratio, ok := result.(TierRatio)
454	if !ok {
455		panic(ufmt.Sprintf("failed to cast result to TierRatio: %T", result))
456	}
457
458	return ratio
459}
460
461func (s *stakerStore) SetPoolTierRatio(_ int, rlm realm, ratio TierRatio) error {
462	if !rlm.IsCurrent() {
463		return errors.New(ErrSpoofedRealm)
464	}
465
466	return s.kvStore.Set(0, rlm, StoreKeyPoolTierRatio.String(), ratio)
467}
468
469// PoolTierCounts
470func (s *stakerStore) HasPoolTierCountsStoreKey() bool {
471	return s.kvStore.Has(StoreKeyPoolTierCounts.String())
472}
473
474func (s *stakerStore) GetPoolTierCounts() [AllTierCount]uint64 {
475	result, err := s.kvStore.Get(StoreKeyPoolTierCounts.String())
476	if err != nil {
477		panic(err)
478	}
479
480	counts, ok := result.([AllTierCount]uint64)
481	if !ok {
482		panic(ufmt.Sprintf("failed to cast result to [AllTierCount]uint64: %T", result))
483	}
484
485	return counts
486}
487
488func (s *stakerStore) SetPoolTierCounts(_ int, rlm realm, counts [AllTierCount]uint64) error {
489	if !rlm.IsCurrent() {
490		return errors.New(ErrSpoofedRealm)
491	}
492
493	return s.kvStore.Set(0, rlm, StoreKeyPoolTierCounts.String(), counts)
494}
495
496// PoolTierLastRewardCacheTimestamp
497func (s *stakerStore) HasPoolTierLastRewardCacheTimestampStoreKey() bool {
498	return s.kvStore.Has(StoreKeyPoolTierLastRewardCacheTimestamp.String())
499}
500
501func (s *stakerStore) GetPoolTierLastRewardCacheTimestamp() int64 {
502	result, err := s.kvStore.Get(StoreKeyPoolTierLastRewardCacheTimestamp.String())
503	if err != nil {
504		panic(err)
505	}
506
507	timestamp, ok := result.(int64)
508	if !ok {
509		panic(ufmt.Sprintf("failed to cast result to int64: %T", result))
510	}
511
512	return timestamp
513}
514
515func (s *stakerStore) SetPoolTierLastRewardCacheTimestamp(_ int, rlm realm, timestamp int64) error {
516	if !rlm.IsCurrent() {
517		return errors.New(ErrSpoofedRealm)
518	}
519
520	return s.kvStore.Set(0, rlm, StoreKeyPoolTierLastRewardCacheTimestamp.String(), timestamp)
521}
522
523// PoolTierCurrentEmission
524func (s *stakerStore) HasPoolTierCurrentEmissionStoreKey() bool {
525	return s.kvStore.Has(StoreKeyPoolTierCurrentEmission.String())
526}
527
528func (s *stakerStore) GetPoolTierCurrentEmission() int64 {
529	result, err := s.kvStore.Get(StoreKeyPoolTierCurrentEmission.String())
530	if err != nil {
531		panic(err)
532	}
533
534	emission, ok := result.(int64)
535	if !ok {
536		panic(ufmt.Sprintf("failed to cast result to int64: %T", result))
537	}
538
539	return emission
540}
541
542func (s *stakerStore) SetPoolTierCurrentEmission(_ int, rlm realm, emission int64) error {
543	if !rlm.IsCurrent() {
544		return errors.New(ErrSpoofedRealm)
545	}
546
547	return s.kvStore.Set(0, rlm, StoreKeyPoolTierCurrentEmission.String(), emission)
548}
549
550// PoolTierGetEmission
551func (s *stakerStore) HasPoolTierGetEmissionStoreKey() bool {
552	return s.kvStore.Has(StoreKeyPoolTierGetEmission.String())
553}
554
555func (s *stakerStore) GetPoolTierGetEmission() func() int64 {
556	result, err := s.kvStore.Get(StoreKeyPoolTierGetEmission.String())
557	if err != nil {
558		panic(err)
559	}
560
561	fn, ok := result.(func() int64)
562	if !ok {
563		panic(ufmt.Sprintf("failed to cast result to func() int64: %T", result))
564	}
565
566	return fn
567}
568
569func (s *stakerStore) SetPoolTierGetEmission(_ int, rlm realm, fn func() int64) error {
570	if !rlm.IsCurrent() {
571		return errors.New(ErrSpoofedRealm)
572	}
573
574	return s.kvStore.Set(0, rlm, StoreKeyPoolTierGetEmission.String(), fn)
575}
576
577// PoolTierGetHalvingBlocksInRange
578func (s *stakerStore) HasPoolTierGetHalvingBlocksInRangeStoreKey() bool {
579	return s.kvStore.Has(StoreKeyPoolTierGetHalvingBlocksInRange.String())
580}
581
582func (s *stakerStore) GetPoolTierGetHalvingBlocksInRange() func(start, end int64) ([]int64, []int64) {
583	result, err := s.kvStore.Get(StoreKeyPoolTierGetHalvingBlocksInRange.String())
584	if err != nil {
585		panic(err)
586	}
587
588	fn, ok := result.(func(start, end int64) ([]int64, []int64))
589	if !ok {
590		panic(ufmt.Sprintf("failed to cast result to func(start, end int64) ([]int64, []int64): %T", result))
591	}
592
593	return fn
594}
595
596func (s *stakerStore) SetPoolTierGetHalvingBlocksInRange(_ int, rlm realm, fn func(start, end int64) ([]int64, []int64)) error {
597	if !rlm.IsCurrent() {
598		return errors.New(ErrSpoofedRealm)
599	}
600
601	return s.kvStore.Set(0, rlm, StoreKeyPoolTierGetHalvingBlocksInRange.String(), fn)
602}
603
604func (s *stakerStore) HasWarmupTemplateStoreKey() bool {
605	return s.kvStore.Has(StoreKeyWarmupTemplate.String())
606}
607
608func (s *stakerStore) GetWarmupTemplate() []Warmup {
609	result, err := s.kvStore.Get(StoreKeyWarmupTemplate.String())
610	if err != nil {
611		panic(err)
612	}
613
614	warmups, ok := result.([]Warmup)
615	if !ok {
616		panic(ufmt.Sprintf("failed to cast result to []Warmup: %T", result))
617	}
618
619	return cloneWarmups(warmups)
620}
621
622func (s *stakerStore) SetWarmupTemplate(_ int, rlm realm, warmups []Warmup) error {
623	if !rlm.IsCurrent() {
624		return errors.New(ErrSpoofedRealm)
625	}
626
627	return s.kvStore.Set(0, rlm, StoreKeyWarmupTemplate.String(), warmups)
628}
629
630// CurrentSwapBatch
631func (s *stakerStore) HasCurrentSwapBatchStoreKey() bool {
632	return s.kvStore.Has(StoreKeyCurrentSwapBatch.String())
633}
634
635func (s *stakerStore) GetCurrentSwapBatch() *SwapBatchProcessor {
636	result, err := s.kvStore.Get(StoreKeyCurrentSwapBatch.String())
637	if err != nil {
638		panic(err)
639	}
640
641	batch, ok := result.(*SwapBatchProcessor)
642	if !ok {
643		panic(ufmt.Sprintf("failed to cast result to *SwapBatchProcessor: %T", result))
644	}
645
646	return batch
647}
648
649func (s *stakerStore) SetCurrentSwapBatch(_ int, rlm realm, batch *SwapBatchProcessor) error {
650	if !rlm.IsCurrent() {
651		return errors.New(ErrSpoofedRealm)
652	}
653
654	return s.kvStore.Set(0, rlm, StoreKeyCurrentSwapBatch.String(), batch)
655}
656
657// NewStakerStore creates a new staker store instance with the provided KV store.
658// This function is used by the upgrade system to create storage instances for each implementation.
659func NewStakerStore(kvStore store.KVStore) IStakerStore {
660	return &stakerStore{
661		kvStore: kvStore,
662	}
663}
664
665func makeIncentiveID(creator address, timestamp int64, index int64) string {
666	return ufmt.Sprintf("%s:%d:%d", creator.String(), timestamp, index)
667}
668
669func contains(items []string, item string) bool {
670	for _, i := range items {
671		if i == item {
672			return true
673		}
674	}
675
676	return false
677}