-
Notifications
You must be signed in to change notification settings - Fork 208
/
keeper.go
191 lines (163 loc) · 7.18 KB
/
keeper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package keeper
import (
"fmt"
"time"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdkerrors "cosmossdk.io/errors"
"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/NibiruChain/collections"
"github.com/NibiruChain/nibiru/x/common/asset"
"github.com/NibiruChain/nibiru/x/oracle/types"
)
// Keeper of the oracle store
type Keeper struct {
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
AccountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
distrKeeper types.DistributionKeeper
StakingKeeper types.StakingKeeper
distrModuleName string
Params collections.Item[types.Params]
ExchangeRates collections.Map[asset.Pair, types.DatedPrice]
FeederDelegations collections.Map[sdk.ValAddress, sdk.AccAddress]
MissCounters collections.Map[sdk.ValAddress, uint64]
Prevotes collections.Map[sdk.ValAddress, types.AggregateExchangeRatePrevote]
Votes collections.Map[sdk.ValAddress, types.AggregateExchangeRateVote]
// PriceSnapshots maps types.PriceSnapshot to the asset.Pair of the snapshot and the creation timestamp as keys.Uint64Key.
PriceSnapshots collections.Map[collections.Pair[asset.Pair, time.Time], types.PriceSnapshot]
WhitelistedPairs collections.KeySet[asset.Pair]
Rewards collections.Map[uint64, types.Rewards]
RewardsID collections.Sequence
}
// NewKeeper constructs a new keeper for oracle
func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey,
accountKeeper types.AccountKeeper,
bankKeeper types.BankKeeper, distrKeeper types.DistributionKeeper,
stakingKeeper types.StakingKeeper, distrName string,
) Keeper {
// ensure oracle module account is set
if addr := accountKeeper.GetModuleAddress(types.ModuleName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
}
return Keeper{
cdc: cdc,
storeKey: storeKey,
AccountKeeper: accountKeeper,
bankKeeper: bankKeeper,
distrKeeper: distrKeeper,
StakingKeeper: stakingKeeper,
distrModuleName: distrName,
Params: collections.NewItem(storeKey, 11, collections.ProtoValueEncoder[types.Params](cdc)),
ExchangeRates: collections.NewMap(storeKey, 1, asset.PairKeyEncoder, collections.ProtoValueEncoder[types.DatedPrice](cdc)),
PriceSnapshots: collections.NewMap(storeKey, 10, collections.PairKeyEncoder(asset.PairKeyEncoder, collections.TimeKeyEncoder), collections.ProtoValueEncoder[types.PriceSnapshot](cdc)),
FeederDelegations: collections.NewMap(storeKey, 2, collections.ValAddressKeyEncoder, collections.AccAddressValueEncoder),
MissCounters: collections.NewMap(storeKey, 3, collections.ValAddressKeyEncoder, collections.Uint64ValueEncoder),
Prevotes: collections.NewMap(storeKey, 4, collections.ValAddressKeyEncoder, collections.ProtoValueEncoder[types.AggregateExchangeRatePrevote](cdc)),
Votes: collections.NewMap(storeKey, 5, collections.ValAddressKeyEncoder, collections.ProtoValueEncoder[types.AggregateExchangeRateVote](cdc)),
WhitelistedPairs: collections.NewKeySet(storeKey, 6, asset.PairKeyEncoder),
Rewards: collections.NewMap(
storeKey, 7,
collections.Uint64KeyEncoder, collections.ProtoValueEncoder[types.Rewards](cdc)),
RewardsID: collections.NewSequence(storeKey, 9),
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// ValidateFeeder return the given feeder is allowed to feed the message or not
func (k Keeper) ValidateFeeder(
ctx sdk.Context, feederAddr sdk.AccAddress, validatorAddr sdk.ValAddress,
) error {
// A validator delegates price feeder consent to itself by default.
// Thus, we only need to verify consent for price feeder addresses that don't
// match the validator address.
if !feederAddr.Equals(validatorAddr) {
delegate := k.FeederDelegations.GetOr(
ctx, validatorAddr, sdk.AccAddress(validatorAddr))
if !delegate.Equals(feederAddr) {
return sdkerrors.Wrapf(
types.ErrNoVotingPermission,
"wanted: %s, got: %s", delegate.String(), feederAddr.String())
}
}
// Check that the given validator is in the active set for consensus.
if val := k.StakingKeeper.Validator(ctx, validatorAddr); val == nil || !val.IsBonded() {
return sdkerrors.Wrapf(
stakingtypes.ErrNoValidatorFound,
"validator %s is not active set", validatorAddr.String())
}
return nil
}
func (k Keeper) GetExchangeRateTwap(ctx sdk.Context, pair asset.Pair) (price sdk.Dec, err error) {
params, err := k.Params.Get(ctx)
if err != nil {
return sdk.OneDec().Neg(), err
}
snapshots := k.PriceSnapshots.Iterate(
ctx,
collections.PairRange[asset.Pair, time.Time]{}.
Prefix(pair).
StartInclusive(
ctx.BlockTime().Add(-1*params.TwapLookbackWindow)).
EndInclusive(
ctx.BlockTime()),
).Values()
if len(snapshots) == 0 {
// if there are no snapshots, return -1 for the price
return sdk.OneDec().Neg(), types.ErrNoValidTWAP.Wrapf("no snapshots for pair %s", pair.String())
}
if len(snapshots) == 1 {
return snapshots[0].Price, nil
}
firstTimestampMs := snapshots[0].TimestampMs
if firstTimestampMs > ctx.BlockTime().UnixMilli() {
// should never happen, or else we have corrupted state
return sdk.OneDec().Neg(), types.ErrNoValidTWAP.Wrapf(
"Possible corrupted state. First timestamp %d is after current blocktime %d", firstTimestampMs, ctx.BlockTime().UnixMilli())
}
if firstTimestampMs == ctx.BlockTime().UnixMilli() {
// shouldn't happen because we check for len(snapshots) == 1, but if it does, return the first snapshot price
return snapshots[0].Price, nil
}
cumulativePrice := sdk.ZeroDec()
for i, s := range snapshots {
var nextTimestampMs int64
if i == len(snapshots)-1 {
// if we're at the last snapshot, then consider that price as ongoing until the current blocktime
nextTimestampMs = ctx.BlockTime().UnixMilli()
} else {
nextTimestampMs = snapshots[i+1].TimestampMs
}
price := s.Price.MulInt64(nextTimestampMs - s.TimestampMs)
cumulativePrice = cumulativePrice.Add(price)
}
return cumulativePrice.QuoInt64(ctx.BlockTime().UnixMilli() - firstTimestampMs), nil
}
func (k Keeper) GetExchangeRate(ctx sdk.Context, pair asset.Pair) (price sdk.Dec, err error) {
exchangeRate, err := k.ExchangeRates.Get(ctx, pair)
price = exchangeRate.ExchangeRate
return
}
// SetPrice sets the price for a pair as well as the price snapshot.
func (k Keeper) SetPrice(ctx sdk.Context, pair asset.Pair, price sdk.Dec) {
k.ExchangeRates.Insert(ctx, pair, types.DatedPrice{ExchangeRate: price, CreatedBlock: uint64(ctx.BlockHeight())})
key := collections.Join(pair, ctx.BlockTime())
timestampMs := ctx.BlockTime().UnixMilli()
k.PriceSnapshots.Insert(ctx, key, types.PriceSnapshot{
Pair: pair,
Price: price,
TimestampMs: timestampMs,
})
if err := ctx.EventManager().EmitTypedEvent(&types.EventPriceUpdate{
Pair: pair.String(),
Price: price,
TimestampMs: timestampMs,
}); err != nil {
ctx.Logger().Error("failed to emit OraclePriceUpdate", "pair", pair, "error", err)
}
}