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

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}