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.45 Kb · 146 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// It includes rate limiting to prevent abuse.
 17type keeper struct {
 18	store   *bptree.BPTree // address(string) -> referral address(string)
 19	lastOps *bptree.BPTree // address(string) -> last operation timestamp(int64)
 20}
 21
 22var _ ReferralKeeper = &keeper{}
 23
 24// NewKeeper creates a new ReferralKeeper instance.
 25func NewKeeper() ReferralKeeper {
 26	return &keeper{
 27		store:   bptree.NewBPTreeN(16),
 28		lastOps: bptree.NewBPTreeN(16),
 29	}
 30}
 31
 32// register creates or updates a referral relationship between addresses.
 33// Setting refAddr to the contract's own address removes the referral.
 34func (k *keeper) register(addr, refAddr address) (address, error) {
 35	if err := k.validateAddresses(addr, refAddr); err != nil {
 36		return zeroAddress, err
 37	}
 38
 39	addrStr := addr.String()
 40	refAddrStr := refAddr.String()
 41
 42	if isRemovalRequest(refAddr) {
 43		if k.has(addr) {
 44			_, ok := k.store.Remove(addrStr)
 45			if !ok {
 46				return zeroAddress, errors.New(ErrNotFound)
 47			}
 48		}
 49
 50		return zeroAddress, nil
 51	}
 52
 53	if err := k.checkRateLimit(addrStr); err != nil {
 54		return zeroAddress, err
 55	}
 56
 57	k.store.Set(addrStr, refAddrStr)
 58	k.lastOps.Set(addrStr, time.Now().Unix())
 59
 60	return refAddr, nil
 61}
 62
 63// validateAddresses validates that addresses are properly formatted and not self-referencing.
 64func (k *keeper) validateAddresses(addr, refAddr address) error {
 65	if !addr.IsValid() || (!isRemovalRequest(refAddr) && !refAddr.IsValid()) {
 66		return errors.New(ErrInvalidAddress)
 67	}
 68	if addr == refAddr {
 69		return errors.New(ErrSelfReferral)
 70	}
 71	return nil
 72}
 73
 74// has returns true if a referral exists for the given address.
 75func (k *keeper) has(addr address) bool {
 76	exists := k.store.Get(addr.String()) != nil
 77	return exists
 78}
 79
 80// get retrieves the referral address for a given address.
 81// Returns ErrNotFound if no referral exists.
 82func (k *keeper) get(addr address) (address, error) {
 83	if !addr.IsValid() {
 84		return zeroAddress, errors.New(ErrInvalidAddress)
 85	}
 86
 87	val := k.store.Get(addr.String())
 88	if val == nil {
 89		return zeroAddress, errors.New(ErrNotFound)
 90	}
 91
 92	refAddr, ok := val.(string)
 93	if !ok {
 94		return zeroAddress, errors.New(ErrInvalidAddress)
 95	}
 96
 97	return address(refAddr), nil
 98}
 99
100// isEmpty returns true if no referrals exist in the store.
101func (k *keeper) isEmpty() bool {
102	return k.store.Size() == 0
103}
104
105// getLastOpTimestamp retrieves the last operation timestamp for a given address.
106// Returns ErrNotFound if no operation exists.
107func (k *keeper) getLastOpTimestamp(addr address) (int64, error) {
108	if !addr.IsValid() {
109		return 0, errors.New(ErrInvalidAddress)
110	}
111
112	val := k.lastOps.Get(addr.String())
113	if val == nil {
114		return 0, errors.New(ErrNotFound)
115	}
116
117	ts, ok := val.(int64)
118	if !ok {
119		return 0, errors.New(ErrInvalidTime)
120	}
121
122	return ts, nil
123}
124
125// checkRateLimit verifies if enough time has passed since the last operation.
126// Returns ErrTooManyRequests if rate limit is exceeded.
127func (k *keeper) checkRateLimit(addr string) error {
128	now := time.Now().Unix()
129
130	lastOpTimeRaw := k.lastOps.Get(addr)
131	if lastOpTimeRaw == nil {
132		return nil
133	}
134
135	lastOpTime, ok := lastOpTimeRaw.(int64)
136	if !ok {
137		return errors.New(ErrInvalidTime)
138	}
139
140	timeSinceLastOp := now - lastOpTime
141	if timeSinceLastOp < MinTimeBetweenUpdates {
142		return errors.New(ErrTooManyRequests)
143	}
144
145	return nil
146}