Skip to content

Commit 03dd3b5

Browse files
authored
Merge branch 'master-feed' into master-tss
2 parents 6869f52 + 19cbea3 commit 03dd3b5

File tree

9 files changed

+104
-38
lines changed

9 files changed

+104
-38
lines changed

grogu/feed/check.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import (
1919

2020
func checkFeeds(c *grogucontext.Context) {
2121
// Fetch parameters, supported feeds, validator prices, and prices
22-
params, feeds, validatorPrices, prices, err := fetchData(c)
22+
params, feeds, validatorPrices, _, err := fetchData(c)
2323
if err != nil {
2424
return
2525
}
2626

2727
signalIDTimestampMap := convertToSignalIDTimestampMap(validatorPrices)
28-
signalIDChainPriceMap := convertToSignalIDChainPriceMap(prices)
28+
signalIDValidatorPriceMap := convertToSignalIDValidatorPriceMap(validatorPrices)
2929

3030
requestedSignalIDs := make(map[string]time.Time)
3131
now := time.Now()
@@ -60,7 +60,7 @@ func checkFeeds(c *grogucontext.Context) {
6060
c.Config.DistributionStartPercentage,
6161
)
6262

63-
if assignedTime.Before(now) || isDeviate(c, feed, signalIDChainPriceMap) {
63+
if assignedTime.Before(now) || isDeviate(c, feed, signalIDValidatorPriceMap) {
6464
updateRequestedSignalID(c, requestedSignalIDs, feed, timestamp, params)
6565
}
6666
}
@@ -141,11 +141,11 @@ func calculateAssignedTime(
141141
return time.Unix(timestamp+2, 0).Add(time.Duration(timeOffset) * time.Second)
142142
}
143143

144-
// isDeviate checks if the current price is deviated from the on-chain price
144+
// isDeviate checks if the current price is deviated from the on-chain validator price
145145
func isDeviate(
146146
c *grogucontext.Context,
147147
feed types.Feed,
148-
signalIDChainPriceMap map[string]uint64,
148+
signalIDValidatorPriceMap map[string]uint64,
149149
) bool {
150150
currentPrices, err := c.PriceService.Query([]string{feed.SignalID})
151151
if err != nil || len(currentPrices) == 0 ||
@@ -158,8 +158,12 @@ func isDeviate(
158158
return false
159159
}
160160

161+
if signalIDValidatorPriceMap[feed.SignalID] == 0 {
162+
return true
163+
}
164+
161165
return feed.DeviationInThousandth <= deviationInThousandth(
162-
signalIDChainPriceMap[feed.SignalID],
166+
signalIDValidatorPriceMap[feed.SignalID],
163167
uint64(price*math.Pow10(9)),
164168
)
165169
}
@@ -189,15 +193,15 @@ func convertToSignalIDTimestampMap(data []types.ValidatorPrice) map[string]int64
189193
return signalIDTimestampMap
190194
}
191195

192-
// convertToSignalIDChainPriceMap converts an array of Prices to a map of signal id to its on-chain prices.
193-
func convertToSignalIDChainPriceMap(data []*types.Price) map[string]uint64 {
194-
signalIDChainPriceMap := make(map[string]uint64)
196+
// convertToSignalIDValidatorPriceMap converts an array of Prices to a map of signal id to its on-chain validator prices.
197+
func convertToSignalIDValidatorPriceMap(data []types.ValidatorPrice) map[string]uint64 {
198+
signalIDValidatorPriceMap := make(map[string]uint64)
195199

196200
for _, entry := range data {
197-
signalIDChainPriceMap[entry.SignalID] = entry.Price
201+
signalIDValidatorPriceMap[entry.SignalID] = entry.Price
198202
}
199203

200-
return signalIDChainPriceMap
204+
return signalIDValidatorPriceMap
201205
}
202206

203207
// deviationInThousandth calculates the deviation in thousandth between two values.

x/feeds/README.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,53 @@ Each abci end block call, the operations to update prices.
314314
At every end block, the Validator Price of all supported feeds will be obtained and checked if it is within the acceptance period (1 interval).
315315
Any validator that does not submit a price within this period is considered to have miss-reported and will be deactivated unless the Supported feeds are in a transition period.
316316
Accepted Validator Prices of the same SignalID will be weighted and median based on the recency of the price and the power of the validator who submitted the price.
317-
The median price is then set as the Price.
317+
The median price is then set as the Price. Here is the price aggregation logic:
318+
319+
#### Input
320+
321+
A list of PriceFeedInfo objects, each containing:
322+
- `Price`: The reported price from the feeder
323+
- `Deviation`: The price deviation
324+
- `Power`: The feeder's power
325+
- `Timestamp`: The time at which the price is reported
326+
327+
#### Objective
328+
329+
- An aggregated price from the list of priceFeedInfo.
330+
331+
#### Assumption
332+
333+
1. No PriceFeedInfo has a power that exceeds 25% of the total power in the list.
334+
335+
#### Procedure
336+
337+
1. Order the List:
338+
339+
- Sort the list by `Timestamp` in descending order (latest timestamp first).
340+
- For entries with the same `Timestamp`, sort by `Power` in descending order.
341+
342+
2. Apply Power Weights:
343+
344+
- Calculate the total power from the list.
345+
- Assign weights to the powers in segments as follows:
346+
- The first 1/32 of the total power is multiplied by 6.
347+
- The next 1/16 of the total power is multiplied by 4.
348+
- The next 1/8 of the total power is multiplied by 2.
349+
- The next 1/4 of the total power is multiplied by 1.1.
350+
- If PriceFeedInfo overlaps between segments, split it into parts corresponding to each segment and assign the respective multiplier.
351+
- Any power that falls outside these segments will have a multiplier of 1.
352+
353+
3. Generate Points:
354+
355+
- For each PriceFeedInfo (or its parts if split), generate three points:
356+
- One at the `Price` with the assigned `Power`.
357+
- One at `Price + Deviation` with the assigned `Power`.
358+
- One at `Price - Deviation` with the assigned `Power`.
359+
360+
4. Calculating Weight Median
361+
362+
- Compute the weighted median of the generated points to determine the final aggregated price.
363+
- The weighted median price is the price at which the cumulative power (sorted by increasing price) crosses half of the total weighted power.
318364

319365
### Update supported feeds
320366

x/feeds/keeper/calculate_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,18 @@ func TestCalculateIntervalAndDeviation(t *testing.T) {
5454
}
5555

5656
func TestSumPower(t *testing.T) {
57-
require.Equal(t, int64(300000), sumPower([]types.Signal{
57+
require.Equal(t, int64(1250009), sumPower([]types.Signal{
5858
{
5959
ID: "crypto_price.bandusd",
6060
Power: 100000,
6161
},
6262
{
6363
ID: "crypto_price.atomusd",
64-
Power: 100000,
64+
Power: 150000,
6565
},
6666
{
6767
ID: "crypto_price.osmousd",
68-
Power: 100000,
68+
Power: 1000009,
6969
},
7070
}))
7171
}

x/feeds/keeper/genesis_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func (suite *KeeperTestSuite) TestExportGenesis() {
5454

5555
func (suite *KeeperTestSuite) TestInitGenesis() {
5656
ctx := suite.ctx
57+
params := types.NewParams("[NOT_SET]", 30, 30, 60, 3600, 1000_000_000, 100, 30, 5, 300, 256, 28800)
5758

5859
delegatorSignals := []types.DelegatorSignals{
5960
{
@@ -86,11 +87,12 @@ func (suite *KeeperTestSuite) TestInitGenesis() {
8687

8788
g := types.DefaultGenesisState()
8889
g.DelegatorSignals = delegatorSignals
90+
g.Params = params
8991

9092
suite.feedsKeeper.InitGenesis(suite.ctx, *g)
9193

9294
suite.Require().Equal(types.DefaultPriceService(), suite.feedsKeeper.GetPriceService(ctx))
93-
suite.Require().Equal(types.DefaultParams(), suite.feedsKeeper.GetParams(ctx))
95+
suite.Require().Equal(params, suite.feedsKeeper.GetParams(ctx))
9496
for _, ds := range delegatorSignals {
9597
suite.Require().
9698
Equal(ds.Signals, suite.feedsKeeper.GetDelegatorSignals(ctx, sdk.MustAccAddressFromBech32(ds.Delegator)))

x/feeds/keeper/grpc_query_test.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,6 @@ func (suite *KeeperTestSuite) TestQueryPrice() {
136136
}
137137
suite.feedsKeeper.SetPrice(ctx, price)
138138

139-
valPrice := types.ValidatorPrice{
140-
PriceStatus: types.PriceStatusAvailable,
141-
Validator: ValidValidator.String(),
142-
SignalID: "crypto_price.bandusd",
143-
Price: 1e9,
144-
Timestamp: ctx.BlockTime().Unix(),
145-
}
146-
err := suite.feedsKeeper.SetValidatorPrice(ctx, valPrice)
147-
suite.Require().NoError(err)
148-
149139
// query and check
150140
res, err := queryClient.Price(gocontext.Background(), &types.QueryPriceRequest{
151141
SignalId: "crypto_price.bandusd",

x/feeds/keeper/keeper_price_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ func (suite *KeeperTestSuite) TestCalculatePrice() {
129129
Price: 2000,
130130
Timestamp: ctx.BlockTime().Unix(),
131131
},
132+
{
133+
PriceStatus: types.PriceStatusAvailable,
134+
Validator: ValidValidator3.String(),
135+
SignalID: "crypto_price.bandusd",
136+
Price: 2000,
137+
Timestamp: ctx.BlockTime().Unix(),
138+
},
132139
})
133140
suite.Require().NoError(err)
134141

x/feeds/keeper/keeper_signal.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,6 @@ func (k Keeper) SetSignalTotalPowers(ctx sdk.Context, signalTotalPowersList []ty
9797
}
9898
}
9999

100-
func (k Keeper) DeleteSignalTotalPower(ctx sdk.Context, signalTotalPower types.Signal) {
101-
k.DeletePrice(ctx, signalTotalPower.ID)
102-
k.deleteSignalTotalPowerByPowerIndex(ctx, signalTotalPower)
103-
ctx.KVStore(k.storeKey).Delete(types.SignalTotalPowerStoreKey(signalTotalPower.ID))
104-
}
105-
106100
func (k Keeper) setSignalTotalPowerByPowerIndex(ctx sdk.Context, signalTotalPower types.Signal) {
107101
ctx.KVStore(k.storeKey).
108102
Set(types.SignalTotalPowerByPowerIndexKey(signalTotalPower.ID, signalTotalPower.Power), []byte(signalTotalPower.ID))

x/feeds/keeper/keeper_test.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ import (
2222
)
2323

2424
var (
25-
ValidValidator = sdk.ValAddress("1234567890")
26-
ValidValidator2 = sdk.ValAddress("2345678901")
27-
ValidDelegator = sdk.AccAddress("3456789012")
28-
ValidDelegator2 = sdk.AccAddress("4567890123")
29-
InvalidValidator = sdk.ValAddress("9876543210")
30-
InvalidDelegator = sdk.AccAddress("8765432109")
25+
ValidValidator = sdk.ValAddress("1000000001")
26+
ValidValidator2 = sdk.ValAddress("1000000002")
27+
ValidValidator3 = sdk.ValAddress("1000000003")
28+
ValidDelegator = sdk.AccAddress("2000000001")
29+
ValidDelegator2 = sdk.AccAddress("2000000002")
30+
InvalidValidator = sdk.ValAddress("9000000001")
31+
InvalidDelegator = sdk.AccAddress("9000000002")
3132
)
3233

3334
type KeeperTestSuite struct {
@@ -63,6 +64,10 @@ func (suite *KeeperTestSuite) SetupTest() {
6364
GetValidatorStatus(gomock.Any(), gomock.Eq(ValidValidator2)).
6465
Return(oracletypes.NewValidatorStatus(true, suite.ctx.BlockHeader().Time)).
6566
AnyTimes()
67+
oracleKeeper.EXPECT().
68+
GetValidatorStatus(gomock.Any(), gomock.Eq(ValidValidator3)).
69+
Return(oracletypes.NewValidatorStatus(true, suite.ctx.BlockHeader().Time)).
70+
AnyTimes()
6671
oracleKeeper.EXPECT().
6772
GetValidatorStatus(gomock.Any(), gomock.Eq(InvalidValidator)).
6873
Return(oracletypes.NewValidatorStatus(false, suite.ctx.BlockHeader().Time)).
@@ -78,6 +83,10 @@ func (suite *KeeperTestSuite) SetupTest() {
7883
GetValidator(gomock.Any(), gomock.Eq(ValidValidator2)).
7984
Return(stakingtypes.Validator{Status: stakingtypes.Bonded}, true).
8085
AnyTimes()
86+
stakingKeeper.EXPECT().
87+
GetValidator(gomock.Any(), gomock.Eq(ValidValidator3)).
88+
Return(stakingtypes.Validator{Status: stakingtypes.Bonded}, true).
89+
AnyTimes()
8190
stakingKeeper.EXPECT().
8291
GetValidator(gomock.Any(), gomock.Eq(InvalidValidator)).
8392
Return(stakingtypes.Validator{Status: stakingtypes.Unbonded}, true).
@@ -94,6 +103,10 @@ func (suite *KeeperTestSuite) SetupTest() {
94103
OperatorAddress: ValidValidator2.String(),
95104
Tokens: sdk.NewInt(3000),
96105
},
106+
{
107+
OperatorAddress: ValidValidator3.String(),
108+
Tokens: sdk.NewInt(3000),
109+
},
97110
}
98111

99112
for i, val := range vals {

x/feeds/types/median_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ func TestCalculateMedianPriceFeedInfo(t *testing.T) {
3636
},
3737
expRes: 109,
3838
},
39+
{
40+
name: "case 3",
41+
priceFeedInfos: []types.PriceFeedInfo{
42+
{Price: 1000, Deviation: 0, Power: 5000, Timestamp: 1716448424, Index: 0},
43+
{Price: 2000, Deviation: 0, Power: 4000, Timestamp: 1716448424, Index: 1},
44+
{Price: 2000, Deviation: 0, Power: 4000, Timestamp: 1716448424, Index: 2},
45+
{Price: 2000, Deviation: 0, Power: 4000, Timestamp: 1716448424, Index: 3},
46+
},
47+
expRes: 1000,
48+
},
3949
}
4050

4151
for _, tc := range testCases {

0 commit comments

Comments
 (0)