Search Apps Documentation Source Content File Folder Download Copy Actions Download

keeper.gno

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