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}