Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions OpenApi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,26 @@ paths:
"204":
description: ""
summary: Withdraw PegIn Collateral
/reports/pegin:
get:
description: ' Get the last pegins on the API. Included in the management API.'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/'
required: true
responses:
"200":
description: ""
summary: Get Pegin Reports
/reports/pegout:
get:
description: ' Get the last pegouts on the API. Included in the management API.'
responses:
"200":
description: ""
summary: Get Pegout Reports
/userQuotes:
get:
description: ' Returns user quotes for address.'
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
github.com/ethereum/go-ethereum v1.14.5
github.com/go-playground/validator/v10 v10.17.0
github.com/gorilla/csrf v1.7.2
Expand Down Expand Up @@ -50,7 +51,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
Expand Down
31 changes: 31 additions & 0 deletions internal/adapters/dataproviders/database/mongo/pegin.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,37 @@ func (repo *peginMongoRepository) GetQuote(ctx context.Context, hash string) (*q
return &result.PeginQuote, nil
}

func (repo *peginMongoRepository) GetQuotes(ctx context.Context, hashes []string) ([]quote.PeginQuote, error) {
dbCtx, cancel := context.WithTimeout(ctx, repo.conn.timeout)
defer cancel()

for _, hash := range hashes {
if err := quote.ValidateQuoteHash(hash); err != nil {
return nil, err
}
}

collection := repo.conn.Collection(PeginQuoteCollection)
filter := bson.M{"hash": bson.M{"$in": hashes}}

quotesReturn := make([]quote.PeginQuote, 0)

cursor, err := collection.Find(dbCtx, filter)
if err != nil {
return nil, err
}
for cursor.Next(ctx) {
var result StoredPeginQuote
err = cursor.Decode(&result)
if err != nil {
return nil, err
}
quotesReturn = append(quotesReturn, result.PeginQuote)
}
logDbInteraction(Read, quotesReturn)
return quotesReturn, nil
}

func (repo *peginMongoRepository) GetRetainedQuote(ctx context.Context, hash string) (*quote.RetainedPeginQuote, error) {
var result quote.RetainedPeginQuote
dbCtx, cancel := context.WithTimeout(ctx, repo.conn.timeout)
Expand Down
43 changes: 43 additions & 0 deletions internal/adapters/dataproviders/database/mongo/pegin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,46 @@ func TestPeginMongoRepository_GetPeginCreationData(t *testing.T) {
assert.Equal(t, quote.PeginCreationDataZeroValue(), result)
})
}

func TestPeginMongoRepository_GetQuotes(t *testing.T) {
t.Run("Successfully retrieves quotes", func(t *testing.T) {
client, collection := getClientAndCollectionMocks(mongo.PeginQuoteCollection)
log.SetLevel(log.DebugLevel)
hashes := []string{testRetainedPegoutQuote.QuoteHash}
repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client, time.Duration(1)))
collection.On("Find", mock.Anything,
bson.M{"hash": bson.M{"$in": hashes}},
).Return(mongoDb.NewCursorFromDocuments([]any{testPeginQuote}, nil, nil)).Once()
result, err := repo.GetQuotes(context.Background(), hashes)
collection.AssertExpectations(t)
require.NoError(t, err)
assert.Equal(t, []quote.PeginQuote{testPeginQuote}, result)
})

t.Run("Fails validation for hashes", func(t *testing.T) {
client, _ := getClientAndCollectionMocks(mongo.PeginQuoteCollection)

invalidHashes := []string{"invalidHash"}
conn := mongo.NewConnection(client, time.Duration(1))
repo := mongo.NewPeginMongoRepository(conn)

_, err := repo.GetQuotes(context.Background(), invalidHashes)
require.Error(t, err)
assert.Equal(t, "invalid quote hash length: expected 64 characters, got 11", err.Error())
})

t.Run("error reading quotes from DB", func(t *testing.T) {
client, collection := getClientAndCollectionMocks(mongo.PeginQuoteCollection)

expectedHashes := []string{testRetainedPegoutQuote.QuoteHash}
collection.On("Find", mock.Anything, bson.M{"hash": bson.M{"$in": expectedHashes}}).Return(nil, mongoDb.ErrNoDocuments).Once()

conn := mongo.NewConnection(client, time.Duration(1))
repo := mongo.NewPeginMongoRepository(conn)

quotes, err := repo.GetQuotes(context.Background(), expectedHashes)
require.Error(t, err)
assert.Equal(t, "mongo: no documents in result", err.Error())
assert.Nil(t, quotes)
})
}
31 changes: 31 additions & 0 deletions internal/adapters/dataproviders/database/mongo/pegout.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,37 @@ func (repo *pegoutMongoRepository) GetQuote(ctx context.Context, hash string) (*
return &result.PegoutQuote, nil
}

func (repo *pegoutMongoRepository) GetQuotes(ctx context.Context, hashes []string) ([]quote.PegoutQuote, error) {
dbCtx, cancel := context.WithTimeout(ctx, repo.conn.timeout)
defer cancel()

for _, hash := range hashes {
if err := quote.ValidateQuoteHash(hash); err != nil {
return nil, err
}
}

collection := repo.conn.Collection(PegoutQuoteCollection)
filter := bson.M{"hash": bson.M{"$in": hashes}}

quotesReturn := make([]quote.PegoutQuote, 0)

cursor, err := collection.Find(dbCtx, filter)
if err != nil {
return nil, err
}
for cursor.Next(ctx) {
var result StoredPegoutQuote
err = cursor.Decode(&result)
if err != nil {
return nil, err
}
quotesReturn = append(quotesReturn, result.PegoutQuote)
}
logDbInteraction(Read, quotesReturn)
return quotesReturn, nil
}

func (repo *pegoutMongoRepository) GetRetainedQuote(ctx context.Context, hash string) (*quote.RetainedPegoutQuote, error) {
var result quote.RetainedPegoutQuote
dbCtx, cancel := context.WithTimeout(ctx, repo.conn.timeout)
Expand Down
43 changes: 43 additions & 0 deletions internal/adapters/dataproviders/database/mongo/pegout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,46 @@ func TestPegoutMongoRepository_GetPegoutCreationData(t *testing.T) {
assert.Equal(t, quote.PegoutCreationDataZeroValue(), result)
})
}

func TestPegoutMongoRepository_GetQuotes(t *testing.T) {
t.Run("Successfully retrieves quotes", func(t *testing.T) {
client, collection := getClientAndCollectionMocks(mongo.PegoutQuoteCollection)
log.SetLevel(log.DebugLevel)
hashes := []string{testRetainedPegoutQuote.QuoteHash}
repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client, time.Duration(1)))
collection.On("Find", mock.Anything,
bson.M{"hash": bson.M{"$in": hashes}},
).Return(mongoDb.NewCursorFromDocuments([]any{testPegoutQuote}, nil, nil)).Once()
result, err := repo.GetQuotes(context.Background(), hashes)
collection.AssertExpectations(t)
require.NoError(t, err)
assert.Equal(t, []quote.PegoutQuote{testPegoutQuote}, result)
})

t.Run("Fails validation for hashes", func(t *testing.T) {
client, _ := getClientAndCollectionMocks(mongo.PegoutQuoteCollection)

invalidHashes := []string{"invalidHash"}
conn := mongo.NewConnection(client, time.Duration(1))
repo := mongo.NewPegoutMongoRepository(conn)

_, err := repo.GetQuotes(context.Background(), invalidHashes)
require.Error(t, err)
assert.Equal(t, "invalid quote hash length: expected 64 characters, got 11", err.Error())
})

t.Run("error reading quotes from DB", func(t *testing.T) {
client, collection := getClientAndCollectionMocks(mongo.PegoutQuoteCollection)

expectedHashes := []string{testRetainedPegoutQuote.QuoteHash}
collection.On("Find", mock.Anything, bson.M{"hash": bson.M{"$in": expectedHashes}}).Return(nil, mongoDb.ErrNoDocuments).Once()

conn := mongo.NewConnection(client, time.Duration(1))
repo := mongo.NewPegoutMongoRepository(conn)

quotes, err := repo.GetQuotes(context.Background(), expectedHashes)
require.Error(t, err)
assert.Equal(t, "mongo: no documents in result", err.Error())
assert.Nil(t, quotes)
})
}
36 changes: 36 additions & 0 deletions internal/adapters/entrypoints/rest/handlers/get_reports_pegin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package handlers

import (
"github.com/rsksmart/liquidity-provider-server/internal/adapters/entrypoints/rest"
"github.com/rsksmart/liquidity-provider-server/internal/usecases/pegin"
"github.com/rsksmart/liquidity-provider-server/pkg"
"net/http"
)

// NewGetReportsPeginHandler
// @Title Get Pegin Reports
// @Description Get the last pegins on the API. Included in the management API.
// @Param PeginReportsRequest body pkg.PeginReportsRequest true "Date range for the report, case not provided will be last 30 days"
// @Success 200 pkg.GetPeginReportResponse
// @Route /reports/pegin [get]
func NewGetReportsPeginHandler(useCase *pegin.GetPeginReportUseCase) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var err error

peginReport, err := useCase.Run(req.Context())
if err != nil {
jsonErr := rest.NewErrorResponseWithDetails(UnknownErrorMessage, rest.DetailsFromError(err), false)
rest.JsonErrorResponse(w, http.StatusInternalServerError, jsonErr)
return
}
response := pkg.GetPeginReportResponse{
NumberOfQuotes: peginReport.NumberOfQuotes,
MinimumQuoteValue: peginReport.MinimumQuoteValue.AsBigInt(),
MaximumQuoteValue: peginReport.MaximumQuoteValue.AsBigInt(),
AverageQuoteValue: peginReport.AverageQuoteValue.AsBigInt(),
TotalFeesCollected: peginReport.TotalFeesCollected.AsBigInt(),
AverageFeePerQuote: peginReport.AverageFeePerQuote.AsBigInt(),
}
rest.JsonResponseWithBody(w, http.StatusOK, &response)
}
}
35 changes: 35 additions & 0 deletions internal/adapters/entrypoints/rest/handlers/get_reports_pegout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package handlers

import (
"github.com/rsksmart/liquidity-provider-server/internal/adapters/entrypoints/rest"
"github.com/rsksmart/liquidity-provider-server/internal/usecases/pegout"
"github.com/rsksmart/liquidity-provider-server/pkg"
"net/http"
)

// NewGetReportsPegoutHandler
// @Title Get Pegout Reports
// @Description Get the last pegouts on the API. Included in the management API.
// @Success 200 pkg.GetPegoutReportResponse
// @Route /reports/pegout [get]
func NewGetReportsPegoutHandler(useCase *pegout.GetPegoutReportUseCase) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var err error

pegoutReport, err := useCase.Run(req.Context())
if err != nil {
jsonErr := rest.NewErrorResponseWithDetails(UnknownErrorMessage, rest.DetailsFromError(err), false)
rest.JsonErrorResponse(w, http.StatusInternalServerError, jsonErr)
return
}
response := pkg.GetPegoutReportResponse{
NumberOfQuotes: pegoutReport.NumberOfQuotes,
MinimumQuoteValue: pegoutReport.MinimumQuoteValue.AsBigInt(),
MaximumQuoteValue: pegoutReport.MaximumQuoteValue.AsBigInt(),
AverageQuoteValue: pegoutReport.AverageQuoteValue.AsBigInt(),
TotalFeesCollected: pegoutReport.TotalFeesCollected.AsBigInt(),
AverageFeePerQuote: pegoutReport.AverageFeePerQuote.AsBigInt(),
}
rest.JsonResponseWithBody(w, http.StatusOK, &response)
}
}
2 changes: 2 additions & 0 deletions internal/adapters/entrypoints/rest/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ type UseCaseRegistry interface {
GetPegoutStatusUseCase() *pegout.StatusUseCase
GetAvailableLiquidityUseCase() *liquidity_provider.GetAvailableLiquidityUseCase
GetServerInfoUseCase() *liquidity_provider.ServerInfoUseCase
GetPeginReportUseCase() *pegin.GetPeginReportUseCase
GetPegoutReportUseCase() *pegout.GetPegoutReportUseCase
}
10 changes: 10 additions & 0 deletions internal/adapters/entrypoints/rest/routes/management.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ func GetManagementEndpoints(env environment.Environment, useCaseRegistry registr
Method: http.MethodPost,
Handler: handlers.NewSetPegoutConfigHandler(useCaseRegistry.SetPegoutConfigUseCase()),
},
{
Path: "/reports/pegin",
Method: http.MethodGet,
Handler: handlers.NewGetReportsPeginHandler(useCaseRegistry.GetPeginReportUseCase()),
},
{
Path: "/reports/pegout",
Method: http.MethodGet,
Handler: handlers.NewGetReportsPegoutHandler(useCaseRegistry.GetPegoutReportUseCase()),
},
{
Path: LoginPath,
Method: http.MethodPost,
Expand Down
4 changes: 3 additions & 1 deletion internal/adapters/entrypoints/rest/routes/management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func TestGetManagementEndpoints(t *testing.T) {
registryMock.EXPECT().SetCredentialsUseCase().Return(&liquidity_provider.SetCredentialsUseCase{})
registryMock.EXPECT().LoginUseCase().Return(&liquidity_provider.LoginUseCase{})
registryMock.EXPECT().GetManagementUiDataUseCase().Return(&liquidity_provider.GetManagementUiDataUseCase{})
registryMock.EXPECT().GetPeginReportUseCase().Return(&pegin.GetPeginReportUseCase{})
registryMock.EXPECT().GetPegoutReportUseCase().Return(&pegout.GetPegoutReportUseCase{})

endpoints := routes.GetManagementEndpoints(environment.Environment{}, registryMock, &mocks.StoreMock{})
specBytes := test.ReadFile(t, "OpenApi.yml")
Expand All @@ -39,7 +41,7 @@ func TestGetManagementEndpoints(t *testing.T) {
err := yaml.Unmarshal(specBytes, spec)
require.NoError(t, err)

assert.Len(t, endpoints, 17)
assert.Len(t, endpoints, 19)
for _, endpoint := range endpoints {
if endpoint.Path != routes.IconPath && endpoint.Path != routes.StaticPath {
lowerCaseMethod := strings.ToLower(endpoint.Method)
Expand Down
2 changes: 2 additions & 0 deletions internal/adapters/entrypoints/rest/routes/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ func setupRegistryMock(registryMock *mocks.UseCaseRegistryMock) {
registryMock.EXPECT().LoginUseCase().Return(&liquidity_provider.LoginUseCase{})
registryMock.EXPECT().GetManagementUiDataUseCase().Return(&liquidity_provider.GetManagementUiDataUseCase{})
registryMock.EXPECT().GetServerInfoUseCase().Return(&liquidity_provider.ServerInfoUseCase{})
registryMock.EXPECT().GetPeginReportUseCase().Return(&pegin.GetPeginReportUseCase{})
registryMock.EXPECT().GetPegoutReportUseCase().Return(&pegout.GetPegoutReportUseCase{})
}

func assertHasCorsHeaders(t *testing.T, recorder *httptest.ResponseRecorder) {
Expand Down
12 changes: 12 additions & 0 deletions internal/configuration/registry/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type UseCaseRegistry struct {
availableLiquidityUseCase *liquidity_provider.GetAvailableLiquidityUseCase
updatePeginDepositUseCase *watcher.UpdatePeginDepositUseCase
getServerInfoUseCase *liquidity_provider.ServerInfoUseCase
getPeginReportUseCase *pegin.GetPeginReportUseCase
getPegoutReportUseCase *pegout.GetPegoutReportUseCase
}

// NewUseCaseRegistry
Expand Down Expand Up @@ -233,6 +235,8 @@ func NewUseCaseRegistry(
availableLiquidityUseCase: liquidity_provider.NewGetAvailableLiquidityUseCase(liquidityProvider, liquidityProvider, liquidityProvider),
updatePeginDepositUseCase: watcher.NewUpdatePeginDepositUseCase(databaseRegistry.PeginRepository),
getServerInfoUseCase: liquidity_provider.NewServerInfoUseCase(),
getPeginReportUseCase: pegin.NewGetPeginReportUseCase(databaseRegistry.PeginRepository),
getPegoutReportUseCase: pegout.NewGetPegoutReportUseCase(databaseRegistry.PegoutRepository),
}
}

Expand Down Expand Up @@ -347,3 +351,11 @@ func (registry *UseCaseRegistry) GetAvailableLiquidityUseCase() *liquidity_provi
func (registry *UseCaseRegistry) GetServerInfoUseCase() *liquidity_provider.ServerInfoUseCase {
return registry.getServerInfoUseCase
}

func (registry *UseCaseRegistry) GetPeginReportUseCase() *pegin.GetPeginReportUseCase {
return registry.getPeginReportUseCase
}

func (registry *UseCaseRegistry) GetPegoutReportUseCase() *pegout.GetPegoutReportUseCase {
return registry.getPegoutReportUseCase
}
1 change: 1 addition & 0 deletions internal/entities/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var (
SerializationError = errors.New("error during value serialization")
IntegrityError = errors.New("error during value integrity check, stored hash doesn't match actual hash")
validate = validator.New(validator.WithRequiredStructEnabled())
DivideByZeroError = errors.New("divide by zero error")
)

func ValidateStruct(s any) error {
Expand Down
2 changes: 1 addition & 1 deletion internal/entities/quote/pegin_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package quote
import (
"context"
"time"

"github.com/rsksmart/liquidity-provider-server/internal/entities"
"github.com/rsksmart/liquidity-provider-server/internal/entities/utils"
)
Expand All @@ -30,6 +29,7 @@ type PeginQuoteRepository interface {
InsertQuote(ctx context.Context, quote CreatedPeginQuote) error
GetQuote(ctx context.Context, hash string) (*PeginQuote, error)
GetPeginCreationData(ctx context.Context, hash string) PeginCreationData
GetQuotes(ctx context.Context, hashes []string) ([]PeginQuote, error)
GetRetainedQuote(ctx context.Context, hash string) (*RetainedPeginQuote, error)
InsertRetainedQuote(ctx context.Context, quote RetainedPeginQuote) error
UpdateRetainedQuote(ctx context.Context, quote RetainedPeginQuote) error
Expand Down
1 change: 1 addition & 0 deletions internal/entities/quote/pegout_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type PegoutQuoteRepository interface {
InsertQuote(ctx context.Context, quote CreatedPegoutQuote) error
GetPegoutCreationData(ctx context.Context, hash string) PegoutCreationData
GetQuote(ctx context.Context, hash string) (*PegoutQuote, error)
GetQuotes(ctx context.Context, hashes []string) ([]PegoutQuote, error)
GetRetainedQuote(ctx context.Context, hash string) (*RetainedPegoutQuote, error)
InsertRetainedQuote(ctx context.Context, quote RetainedPegoutQuote) error
ListPegoutDepositsByAddress(ctx context.Context, address string) ([]PegoutDeposit, error)
Expand Down
Loading
Loading