Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

keeper.gno

3.71 Kb · 151 lines
  1package referral
  2
  3import (
  4	"errors"
  5	"time"
  6
  7	bptree "gno.land/p/nt/bptree/v0"
  8)
  9
 10const (
 11	// MinTimeBetweenUpdates is minimum duration between operations (24 hours).
 12	MinTimeBetweenUpdates int64 = 24 * 60 * 60
 13)
 14
 15// keeper implements ReferralKeeper using BPTree storage.
 16// Non-removal registrations and updates use a 24-hour cooldown; removal via
 17// the contract-address sentinel bypasses that check and preserves the timestamp.
 18type keeper struct {
 19	store   *bptree.BPTree // address(string) -> referral address(string)
 20	lastOps *bptree.BPTree // address(string) -> last operation timestamp(int64)
 21}
 22
 23var _ ReferralKeeper = &keeper{}
 24
 25// NewKeeper creates an empty ReferralKeeper backed by independent referral and
 26// last-operation BPTrees.
 27//
 28// Returns:
 29//   - keeper: new referral store with no relationships or operation timestamps
 30func NewKeeper() ReferralKeeper {
 31	return &keeper{
 32		store:   bptree.NewBPTreeN(16),
 33		lastOps: bptree.NewBPTreeN(16),
 34	}
 35}
 36
 37// register creates or updates a referral relationship between addresses.
 38// Setting refAddr to the contract's own address removes the referral.
 39func (k *keeper) register(addr, refAddr address) (address, error) {
 40	if err := k.validateAddresses(addr, refAddr); err != nil {
 41		return zeroAddress, err
 42	}
 43
 44	addrStr := addr.String()
 45	refAddrStr := refAddr.String()
 46
 47	if isRemovalRequest(refAddr) {
 48		if k.has(addr) {
 49			_, ok := k.store.Remove(addrStr)
 50			if !ok {
 51				return zeroAddress, errors.New(ErrNotFound)
 52			}
 53		}
 54
 55		return zeroAddress, nil
 56	}
 57
 58	if err := k.checkRateLimit(addrStr); err != nil {
 59		return zeroAddress, err
 60	}
 61
 62	k.store.Set(addrStr, refAddrStr)
 63	k.lastOps.Set(addrStr, time.Now().Unix())
 64
 65	return refAddr, nil
 66}
 67
 68// validateAddresses validates that addresses are properly formatted and not self-referencing.
 69func (k *keeper) validateAddresses(addr, refAddr address) error {
 70	if !addr.IsValid() || (!isRemovalRequest(refAddr) && !refAddr.IsValid()) {
 71		return errors.New(ErrInvalidAddress)
 72	}
 73	if addr == refAddr {
 74		return errors.New(ErrSelfReferral)
 75	}
 76	return nil
 77}
 78
 79// has returns true if a referral exists for the given address.
 80func (k *keeper) has(addr address) bool {
 81	exists := k.store.Get(addr.String()) != nil
 82	return exists
 83}
 84
 85// get retrieves the referral address for a given address.
 86// Returns ErrNotFound if no referral exists.
 87func (k *keeper) get(addr address) (address, error) {
 88	if !addr.IsValid() {
 89		return zeroAddress, errors.New(ErrInvalidAddress)
 90	}
 91
 92	val := k.store.Get(addr.String())
 93	if val == nil {
 94		return zeroAddress, errors.New(ErrNotFound)
 95	}
 96
 97	refAddr, ok := val.(string)
 98	if !ok {
 99		return zeroAddress, errors.New(ErrInvalidAddress)
100	}
101
102	return address(refAddr), nil
103}
104
105// isEmpty returns true if no referrals exist in the store.
106func (k *keeper) isEmpty() bool {
107	return k.store.Size() == 0
108}
109
110// getLastOpTimestamp retrieves the last operation timestamp for a given address.
111// Returns ErrNotFound if no operation exists.
112func (k *keeper) getLastOpTimestamp(addr address) (int64, error) {
113	if !addr.IsValid() {
114		return 0, errors.New(ErrInvalidAddress)
115	}
116
117	val := k.lastOps.Get(addr.String())
118	if val == nil {
119		return 0, errors.New(ErrNotFound)
120	}
121
122	ts, ok := val.(int64)
123	if !ok {
124		return 0, errors.New(ErrInvalidTime)
125	}
126
127	return ts, nil
128}
129
130// checkRateLimit verifies if enough time has passed since the last operation.
131// Returns ErrTooManyRequests if rate limit is exceeded.
132func (k *keeper) checkRateLimit(addr string) error {
133	now := time.Now().Unix()
134
135	lastOpTimeRaw := k.lastOps.Get(addr)
136	if lastOpTimeRaw == nil {
137		return nil
138	}
139
140	lastOpTime, ok := lastOpTimeRaw.(int64)
141	if !ok {
142		return errors.New(ErrInvalidTime)
143	}
144
145	timeSinceLastOp := now - lastOpTime
146	if timeSinceLastOp < MinTimeBetweenUpdates {
147		return errors.New(ErrTooManyRequests)
148	}
149
150	return nil
151}