Skip to content

Commit 90a9dc0

Browse files
Merge pull request #855 from rsksmart/fix/FLY-2091/revenue_report
Fix/ FLY-2091/ revenue report bug
2 parents 94b1b6e + 42ab508 commit 90a9dc0

File tree

4 files changed

+209
-2
lines changed

4 files changed

+209
-2
lines changed

internal/adapters/dataproviders/database/mongo/pegin.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,21 @@ func (repo *peginMongoRepository) GetRetainedQuotesForAddress(ctx context.Contex
456456
return result, nil
457457
}
458458

459+
// GetQuotesWithRetainedByStateAndDate retrieves pegin quotes filtered by state and date range,
460+
// optionally joined with their retained data.
461+
//
462+
// IMPORTANT: This method may return quotes WITHOUT retained data (non-accepted quotes).
463+
// The aggregation pipeline includes quotes that have no matching RetainedPeginQuote
464+
// record. For these quotes, the RetainedQuote field will be a zero-valued struct:
465+
// - QuoteHash: "" (empty string)
466+
// - DepositAddress: ""
467+
// - State: ""
468+
// - All numeric fields: 0
469+
// - All Wei pointers: set to NewWei(0) by FillZeroValues()
470+
//
471+
// The states parameter filters by RetainedQuote state when retained data exists, or
472+
// includes quotes without retained data regardless of the provided states.
473+
//
459474
// TODO: add pagination to this method
460475
func (repo *peginMongoRepository) GetQuotesWithRetainedByStateAndDate(ctx context.Context, states []quote.PeginState, startDate, endDate time.Time) ([]quote.PeginQuoteWithRetained, error) {
461476
dbCtx, cancel := context.WithTimeout(ctx, repo.conn.timeout)

internal/adapters/dataproviders/database/mongo/pegout.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,20 @@ func (repo *pegoutMongoRepository) GetRetainedQuotesInBatch(ctx context.Context,
593593
return result, nil
594594
}
595595

596+
// GetQuotesWithRetainedByStateAndDate retrieves pegout quotes filtered by state and date range,
597+
// optionally joined with their retained data.
598+
//
599+
// IMPORTANT: This method may return quotes WITHOUT retained data (non-accepted quotes).
600+
// The aggregation pipeline includes quotes that have no matching RetainedPegoutQuote
601+
// record. For these quotes, the RetainedQuote field will be a zero-valued struct:
602+
// - QuoteHash: "" (empty string)
603+
// - DepositAddress: ""
604+
// - State: ""
605+
// - All numeric fields: 0
606+
// - All Wei pointers: set to NewWei(0)
607+
//
608+
// The states parameter filters by RetainedQuote state when retained data exists, or
609+
// includes quotes without retained data regardless of the provided states.
596610
func (repo *pegoutMongoRepository) GetQuotesWithRetainedByStateAndDate(ctx context.Context, states []quote.PegoutState, startDate, endDate time.Time) ([]quote.PegoutQuoteWithRetained, error) {
597611
dbCtx, cancel := context.WithTimeout(ctx, repo.conn.timeout)
598612
defer cancel()

internal/usecases/reports/get_revenue_report.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,22 @@ func (useCase *GetRevenueReportUseCase) getPeginQuotes(
152152
endDate time.Time,
153153
) ([]quote.PeginQuoteWithRetained, error) {
154154
peginStates := []quote.PeginState{quote.PeginStateRegisterPegInSucceeded}
155-
return useCase.peginQuoteRepository.GetQuotesWithRetainedByStateAndDate(ctx, peginStates, startDate, endDate)
155+
quotes, err := useCase.peginQuoteRepository.GetQuotesWithRetainedByStateAndDate(ctx, peginStates, startDate, endDate)
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
// Filter out quotes without retained data (non-accepted quotes).
161+
// The repository's aggregation pipeline may include quotes without retained data
162+
// due to its design for other use cases. Revenue report only processes completed
163+
// transactions that have valid retained quote data.
164+
result := make([]quote.PeginQuoteWithRetained, 0, len(quotes))
165+
for _, q := range quotes {
166+
if q.RetainedQuote.QuoteHash != "" {
167+
result = append(result, q)
168+
}
169+
}
170+
return result, nil
156171
}
157172

158173
func (useCase *GetRevenueReportUseCase) getPegoutQuotes(
@@ -161,7 +176,22 @@ func (useCase *GetRevenueReportUseCase) getPegoutQuotes(
161176
endDate time.Time,
162177
) ([]quote.PegoutQuoteWithRetained, error) {
163178
pegoutStates := []quote.PegoutState{quote.PegoutStateRefundPegOutSucceeded, quote.PegoutStateBridgeTxSucceeded, quote.PegoutStateBtcReleased}
164-
return useCase.pegoutQuoteRepository.GetQuotesWithRetainedByStateAndDate(ctx, pegoutStates, startDate, endDate)
179+
quotes, err := useCase.pegoutQuoteRepository.GetQuotesWithRetainedByStateAndDate(ctx, pegoutStates, startDate, endDate)
180+
if err != nil {
181+
return nil, err
182+
}
183+
184+
// Filter out quotes without retained data (non-accepted quotes).
185+
// The repository's aggregation pipeline may include quotes without retained data
186+
// due to its design for other use cases. Revenue report only processes completed
187+
// transactions that have valid retained quote data.
188+
result := make([]quote.PegoutQuoteWithRetained, 0, len(quotes))
189+
for _, q := range quotes {
190+
if q.RetainedQuote.QuoteHash != "" {
191+
result = append(result, q)
192+
}
193+
}
194+
return result, nil
165195
}
166196

167197
func (useCase *GetRevenueReportUseCase) getPenalizations(

internal/usecases/reports/get_revenue_report_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,3 +718,151 @@ func TestGetRevenueReportUseCase_Run_MultipleQuotesWithComplexScenario(t *testin
718718
assert.Equal(t, expectedTotalGasSpent, result.TotalGasSpent, "TotalGasSpent mismatch")
719719
assert.Equal(t, expectedTotalPenalizations, result.TotalPenalizations, "TotalPenalizations mismatch")
720720
}
721+
722+
// nolint:funlen
723+
func TestGetRevenueReportUseCase_Run_ExcludesQuotesWithoutRetainedData(t *testing.T) {
724+
ctx := context.Background()
725+
startDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
726+
endDate := time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)
727+
728+
peginQuotesWithRetained := []quote.PeginQuoteWithRetained{
729+
{
730+
Quote: quote.PeginQuote{
731+
CallFee: entities.NewWei(1000),
732+
GasFee: entities.NewWei(350000),
733+
},
734+
RetainedQuote: quote.RetainedPeginQuote{
735+
QuoteHash: "valid-pegin-hash",
736+
CallForUserGasUsed: 21000,
737+
CallForUserGasPrice: entities.NewWei(5),
738+
RegisterPeginGasUsed: 50000,
739+
RegisterPeginGasPrice: entities.NewWei(4),
740+
},
741+
},
742+
{
743+
Quote: quote.PeginQuote{
744+
CallFee: entities.NewWei(2000),
745+
GasFee: entities.NewWei(500000),
746+
},
747+
RetainedQuote: quote.RetainedPeginQuote{
748+
QuoteHash: "",
749+
},
750+
},
751+
}
752+
753+
pegoutQuotesWithRetained := []quote.PegoutQuoteWithRetained{
754+
{
755+
Quote: quote.PegoutQuote{
756+
CallFee: entities.NewWei(1500),
757+
GasFee: entities.NewWei(300000),
758+
},
759+
RetainedQuote: quote.RetainedPegoutQuote{
760+
QuoteHash: "valid-pegout-hash",
761+
RefundPegoutGasUsed: 40000,
762+
RefundPegoutGasPrice: entities.NewWei(4),
763+
BridgeRefundGasUsed: 30000,
764+
BridgeRefundGasPrice: entities.NewWei(3),
765+
SendPegoutBtcFee: entities.NewWei(1000),
766+
},
767+
},
768+
{
769+
Quote: quote.PegoutQuote{
770+
CallFee: entities.NewWei(2500),
771+
GasFee: entities.NewWei(600000),
772+
},
773+
RetainedQuote: quote.RetainedPegoutQuote{
774+
QuoteHash: "",
775+
},
776+
},
777+
}
778+
779+
// Expected calculations:
780+
// Pegin gas spent = (21000*5 + 50000*4) = 105000 + 200000 = 305000
781+
// Pegout gas spent = (40000*4 + 30000*3 + 1000) = 160000 + 90000 + 1000 = 251000
782+
// Total gas spent = 305000 + 251000 = 556000
783+
expectedTotalQuoteCallFees := entities.NewWei(2500)
784+
expectedTotalGasFeesCollected := entities.NewWei(650000)
785+
expectedTotalGasSpent := entities.NewWei(556000)
786+
expectedTotalPenalizations := entities.NewWei(0)
787+
788+
peginQuoteRepo := &mocks.PeginQuoteRepositoryMock{}
789+
pegoutQuoteRepo := &mocks.PegoutQuoteRepositoryMock{}
790+
penalizationRepo := &mocks.PenalizedEventRepositoryMock{}
791+
792+
peginQuoteRepo.On("GetQuotesWithRetainedByStateAndDate", ctx, []quote.PeginState{quote.PeginStateRegisterPegInSucceeded}, startDate, endDate).
793+
Return(peginQuotesWithRetained, nil).Once()
794+
795+
pegoutQuoteRepo.On("GetQuotesWithRetainedByStateAndDate", ctx, []quote.PegoutState{quote.PegoutStateRefundPegOutSucceeded, quote.PegoutStateBridgeTxSucceeded, quote.PegoutStateBtcReleased}, startDate, endDate).
796+
Return(pegoutQuotesWithRetained, nil).Once()
797+
798+
penalizationRepo.On("GetPenalizationsByQuoteHashes", ctx, []string{"valid-pegin-hash", "valid-pegout-hash"}).
799+
Return([]penalization.PenalizedEvent{}, nil).Once()
800+
801+
useCase := reports.NewGetRevenueReportUseCase(peginQuoteRepo, pegoutQuoteRepo, penalizationRepo)
802+
803+
result, err := useCase.Run(ctx, startDate, endDate)
804+
805+
require.NoError(t, err)
806+
peginQuoteRepo.AssertExpectations(t)
807+
pegoutQuoteRepo.AssertExpectations(t)
808+
penalizationRepo.AssertExpectations(t)
809+
810+
assert.Equal(t, expectedTotalQuoteCallFees, result.TotalQuoteCallFees)
811+
assert.Equal(t, expectedTotalGasFeesCollected, result.TotalGasFeesCollected)
812+
assert.Equal(t, expectedTotalGasSpent, result.TotalGasSpent)
813+
assert.Equal(t, expectedTotalPenalizations, result.TotalPenalizations)
814+
}
815+
816+
func TestGetRevenueReportUseCase_Run_AllQuotesWithoutRetainedData(t *testing.T) {
817+
ctx := context.Background()
818+
startDate := time.Date(2024, 5, 1, 0, 0, 0, 0, time.UTC)
819+
endDate := time.Date(2024, 5, 31, 23, 59, 59, 0, time.UTC)
820+
821+
peginQuotesWithRetained := []quote.PeginQuoteWithRetained{
822+
{
823+
Quote: quote.PeginQuote{
824+
CallFee: entities.NewWei(500),
825+
GasFee: entities.NewWei(200000),
826+
},
827+
RetainedQuote: quote.RetainedPeginQuote{
828+
QuoteHash: "",
829+
},
830+
},
831+
{
832+
Quote: quote.PeginQuote{
833+
CallFee: entities.NewWei(700),
834+
GasFee: entities.NewWei(300000),
835+
},
836+
RetainedQuote: quote.RetainedPeginQuote{
837+
QuoteHash: "",
838+
},
839+
},
840+
}
841+
842+
peginQuoteRepo := &mocks.PeginQuoteRepositoryMock{}
843+
pegoutQuoteRepo := &mocks.PegoutQuoteRepositoryMock{}
844+
penalizationRepo := &mocks.PenalizedEventRepositoryMock{}
845+
846+
peginQuoteRepo.On("GetQuotesWithRetainedByStateAndDate", ctx, []quote.PeginState{quote.PeginStateRegisterPegInSucceeded}, startDate, endDate).
847+
Return(peginQuotesWithRetained, nil).Once()
848+
849+
pegoutQuoteRepo.On("GetQuotesWithRetainedByStateAndDate", ctx, []quote.PegoutState{quote.PegoutStateRefundPegOutSucceeded, quote.PegoutStateBridgeTxSucceeded, quote.PegoutStateBtcReleased}, startDate, endDate).
850+
Return([]quote.PegoutQuoteWithRetained{}, nil).Once()
851+
852+
penalizationRepo.On("GetPenalizationsByQuoteHashes", ctx, []string{}).
853+
Return([]penalization.PenalizedEvent{}, nil).Once()
854+
855+
useCase := reports.NewGetRevenueReportUseCase(peginQuoteRepo, pegoutQuoteRepo, penalizationRepo)
856+
857+
result, err := useCase.Run(ctx, startDate, endDate)
858+
859+
require.NoError(t, err)
860+
peginQuoteRepo.AssertExpectations(t)
861+
pegoutQuoteRepo.AssertExpectations(t)
862+
penalizationRepo.AssertExpectations(t)
863+
864+
assert.Equal(t, entities.NewWei(0), result.TotalQuoteCallFees)
865+
assert.Equal(t, entities.NewWei(0), result.TotalGasFeesCollected)
866+
assert.Equal(t, entities.NewWei(0), result.TotalGasSpent)
867+
assert.Equal(t, entities.NewWei(0), result.TotalPenalizations)
868+
}

0 commit comments

Comments
 (0)