From 8cd2906722898029c9d1243ee986b2fb8bd7e4a4 Mon Sep 17 00:00:00 2001 From: colmazia Date: Mon, 25 Nov 2024 17:08:30 +0700 Subject: [PATCH] add status check to grogu update price checker, handle case divide by zero in deviation check --- grogu/signaller/signaller.go | 34 +++++------ grogu/signaller/signaller_test.go | 95 +++++++++++++++---------------- grogu/signaller/utils.go | 7 ++- grogu/signaller/utils_test.go | 3 + 4 files changed, 70 insertions(+), 69 deletions(-) diff --git a/grogu/signaller/signaller.go b/grogu/signaller/signaller.go index 4a6065986..11ccb47f0 100644 --- a/grogu/signaller/signaller.go +++ b/grogu/signaller/signaller.go @@ -240,16 +240,16 @@ func (s *Signaller) filterAndPrepareSignalPrices( continue } - if !s.isPriceValid(price, currentTime) { - continue - } - signalPrice, err := convertPriceData(price) if err != nil { s.logger.Debug("[Signaller] failed to parse price data: %v", err) continue } + if !s.isPriceValid(signalPrice, currentTime) { + continue + } + if s.isNonUrgentUnavailablePrices(signalPrice, currentTime.Unix()) { s.logger.Debug("[Signaller] non-urgent unavailable price: %v", signalPrice) continue @@ -278,38 +278,34 @@ func (s *Signaller) isNonUrgentUnavailablePrices( } func (s *Signaller) isPriceValid( - price *bothan.Price, + newPrice types.SignalPrice, now time.Time, ) bool { // Check if the price is supported and required to be submitted - feed, ok := s.signalIDToFeed[price.SignalId] + feed, ok := s.signalIDToFeed[newPrice.SignalID] if !ok { return false } // Get the last price submitted by the validator, if it doesn't exist, it is valid to be sent - valPrice, ok := s.signalIDToValidatorPrice[price.SignalId] + oldPrice, ok := s.signalIDToValidatorPrice[newPrice.SignalID] if !ok { return true } // If the last price exists, check if the price can be updated - if s.shouldUpdatePrice(feed, valPrice, price.Price, now) { - return true - } - - return false + return s.shouldUpdatePrice(feed, oldPrice, newPrice, now) } func (s *Signaller) shouldUpdatePrice( feed types.FeedWithDeviation, - valPrice types.ValidatorPrice, - newPrice uint64, + oldPrice types.ValidatorPrice, + newPrice types.SignalPrice, now time.Time, ) bool { // thresholdTime is the time when the price can be updated. // add TimeBuffer to make sure the thresholdTime is not too early. - thresholdTime := time.Unix(valPrice.Timestamp+s.params.CooldownTime+TimeBuffer, 0) + thresholdTime := time.Unix(oldPrice.Timestamp+s.params.CooldownTime+TimeBuffer, 0) if now.Before(thresholdTime) { return false @@ -319,7 +315,7 @@ func (s *Signaller) shouldUpdatePrice( assignedTime := calculateAssignedTime( s.valAddress, feed.Interval, - valPrice.Timestamp, + oldPrice.Timestamp, s.distributionOffsetPercentage, s.distributionStartPercentage, ) @@ -328,10 +324,10 @@ func (s *Signaller) shouldUpdatePrice( return true } - // Check if the price is deviated from the last submission, if it is, add it to the list of prices to update - if isDeviated(feed.DeviationBasisPoint, valPrice.Price, newPrice) { + if oldPrice.SignalPriceStatus != newPrice.Status { return true } - return false + // Check if the price is deviated from the last submission, if it is, add it to the list of prices to update + return isDeviated(feed.DeviationBasisPoint, oldPrice.Price, newPrice.Price) } diff --git a/grogu/signaller/signaller_test.go b/grogu/signaller/signaller_test.go index 93b669a45..4ef3e0016 100644 --- a/grogu/signaller/signaller_test.go +++ b/grogu/signaller/signaller_test.go @@ -67,6 +67,12 @@ func (s *SignallerTestSuite) SetupTest() { Interval: 60, DeviationBasisPoint: 50, }, + { + SignalID: "signal2", + Power: 60000000000, + Interval: 60, + DeviationBasisPoint: 50, + }, }, }}, nil). AnyTimes() @@ -224,25 +230,18 @@ func (s *SignallerTestSuite) TestIsPriceValid() { // Update internal variables s.TestUpdateInternalVariables() - priceData := &bothan.Price{ - SignalId: "signal1", - Price: 10000, - Status: bothan.Status_STATUS_AVAILABLE, + priceData := feeds.SignalPrice{ + Status: feeds.SIGNAL_PRICE_STATUS_AVAILABLE, + Price: 10000, } - // Test with time before the assigned time - beforeAssignedTime := time.Unix(s.assignedTime.Unix()-1, 0) - isValid := s.Signaller.isPriceValid(priceData, beforeAssignedTime) - s.Require().False(isValid) + // Test with price is not required to be submitted + priceData.SignalID = "signal3" + s.Require().False(s.Signaller.isPriceValid(priceData, s.assignedTime)) - // Test with time at the assigned time - isValid = s.Signaller.isPriceValid(priceData, s.assignedTime) - s.Require().True(isValid) - - // Test with time at the start of the interval - startOfInterval := time.Unix(0, 0) - isValid = s.Signaller.isPriceValid(priceData, startOfInterval) - s.Require().False(isValid) + // Test with price is required to be submitted and not exist yet + priceData.SignalID = "signal2" + s.Require().True(s.Signaller.isPriceValid(priceData, s.assignedTime)) } func (s *SignallerTestSuite) TestShouldUpdatePrice() { @@ -256,46 +255,44 @@ func (s *SignallerTestSuite) TestShouldUpdatePrice() { } valPrice := feeds.ValidatorPrice{ - SignalID: "signal1", - Price: 10000, - Timestamp: 0, + SignalID: "signal1", + Price: 10000, + Timestamp: 0, + SignalPriceStatus: feeds.SIGNAL_PRICE_STATUS_AVAILABLE, } - // Test with new price positive deviation - thresholdTime := time.Unix(valPrice.Timestamp+s.Signaller.params.CooldownTime+TimeBuffer, 0) - newPrice := uint64(10050) - - shouldUpdate := s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime) - s.Require().True(shouldUpdate) - - // Test with new price negative deviation - newPrice = uint64(9950) - - shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime) - s.Require().True(shouldUpdate) - - // Test with new price within deviation - newPrice = uint64(10025) + newPrice := feeds.SignalPrice{ + Price: 10000, + Status: feeds.SIGNAL_PRICE_STATUS_AVAILABLE, + } - shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime) - s.Require().False(shouldUpdate) + thresholdTime := time.Unix(valPrice.Timestamp+s.Signaller.params.CooldownTime+TimeBuffer, 0) - // Test with new price outside deviation - newPrice = uint64(10075) + // Test case: Time before thresholdTime, should not update + s.Require().False(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime.Add(-time.Second))) - shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime) - s.Require().True(shouldUpdate) + // Test case: Time after thresholdTime and assignedTime + assignedTime := calculateAssignedTime( + s.Signaller.valAddress, + feed.Interval, + valPrice.Timestamp, + s.Signaller.distributionOffsetPercentage, + s.Signaller.distributionStartPercentage, + ) + s.Require().True(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(time.Second))) - // Test with time before threshold time, price outside deviation - newPrice = uint64(10075) - beforeThresholdTime := time.Unix(valPrice.Timestamp+s.Signaller.params.CooldownTime, 0) + // Test case: SignalPriceStatus changed, should update + newPrice.Status = feeds.SIGNAL_PRICE_STATUS_AVAILABLE + s.Require().False(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second))) - shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, beforeThresholdTime) - s.Require().False(shouldUpdate) + newPrice.Status = feeds.SIGNAL_PRICE_STATUS_UNAVAILABLE + s.Require().True(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second))) - // Test with time at assigned time, price within deviation - newPrice = uint64(10025) + // Test case: Price deviated + newPrice.Status = feeds.SIGNAL_PRICE_STATUS_AVAILABLE + newPrice.Price = 11000 // More than deviationBasisPoint + s.Require().True(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second))) - shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, s.assignedTime) - s.Require().True(shouldUpdate) + newPrice.Price = 10025 // Within deviationBasisPoint + s.Require().False(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second))) } diff --git a/grogu/signaller/utils.go b/grogu/signaller/utils.go index 5bf8d1936..5506cba80 100644 --- a/grogu/signaller/utils.go +++ b/grogu/signaller/utils.go @@ -17,7 +17,7 @@ import ( // exceeds a given threshold in basis points. // Parameters: -// - deviationBasisPoint: the allowable deviation in basis points (1/1000th) +// - deviationBasisPoint: the allowable deviation in basis points (1/10000th) // - oldPrice: the original price // - newPrice: the new price to compare against the original // @@ -31,6 +31,11 @@ import ( // original price and multiplying by 10000. // 3. Check if the calculated deviation meets or exceeds the allowable deviation. func isDeviated(deviationBasisPoint int64, oldPrice uint64, newPrice uint64) bool { + // If the old price is zero and the new price is non-zero, consider it a deviation + if oldPrice == 0 { + return newPrice != 0 + } + // Calculate the deviation diff := math.Abs(float64(newPrice) - float64(oldPrice)) dev := int64((diff * 10000) / float64(oldPrice)) diff --git a/grogu/signaller/utils_test.go b/grogu/signaller/utils_test.go index fde5ee997..4deedef2b 100644 --- a/grogu/signaller/utils_test.go +++ b/grogu/signaller/utils_test.go @@ -24,6 +24,9 @@ func TestIsDeviated(t *testing.T) { {"Below threshold", 100, 1000, 1001, false}, {"Exact threshold", 100, 1000, 1010, true}, {"Above threshold", 100, 1000, 1100, true}, + {"Zero old price", 100, 0, 1000, true}, + {"Zero new price", 100, 1000, 0, true}, + {"Zero old and new price", 100, 0, 0, false}, } for _, tt := range tests {