terra/price_service.go

91 lines
2.2 KiB
Go

package terra
import (
"context"
"sync"
"time"
"github.com/pkg/errors"
"github.com/shopspring/decimal"
)
type PriceService struct {
routers []Router
cache map[string]cachedPrice
cacheMutex *sync.Mutex
cacheTimeout time.Duration
maxRouteLength int
}
type cachedPrice struct {
price decimal.Decimal
updatedTime time.Time
}
type PriceServiceOption func(s *PriceService) *PriceService
func WithCacheTimeout(timeout time.Duration) PriceServiceOption {
return func(s *PriceService) *PriceService {
s.cacheTimeout = timeout
return s
}
}
func WithMaxRouteLength(maxRouteLenght int) PriceServiceOption {
return func(s *PriceService) *PriceService {
s.maxRouteLength = maxRouteLenght
return s
}
}
func NewPriceService(options ...PriceServiceOption) *PriceService {
p := &PriceService{
routers: nil,
cache: make(map[string]cachedPrice),
cacheMutex: &sync.Mutex{},
cacheTimeout: 30 * time.Second,
maxRouteLength: 2,
}
for _, option := range options {
p = option(p)
}
return p
}
func (s *PriceService) AddRouter(router Router) {
s.routers = append(s.routers, router)
}
func (s *PriceService) GetPriceCached(ctx context.Context, token Token) (decimal.Decimal, error) {
s.cacheMutex.Lock()
defer s.cacheMutex.Unlock()
if p, ok := s.cache[token.Id()]; ok && p.updatedTime.Add(s.cacheTimeout).After(time.Now()) {
return p.price, nil
}
p, err := s.getCurrentPrice(ctx, token)
if err != nil {
return decimal.Zero, errors.Wrap(err, "getting current price")
}
s.cache[token.Id()] = cachedPrice{
price: p,
updatedTime: time.Now(),
}
return p, nil
}
func (s *PriceService) getCurrentPrice(ctx context.Context, token Token) (decimal.Decimal, error) {
var bestprice decimal.Decimal
var bestroute Route
for _, router := range s.routers {
newprice, newroute, err := router.SimulateSwap(ctx, token, UST, decimal.NewFromInt(1), s.maxRouteLength)
if err != nil && err != ErrNoRouteFund {
return decimal.Zero, errors.Wrapf(err, "simulating swap with router %s", router)
}
if newprice.GreaterThan(bestprice) || (newprice.Equals(bestprice) && len(newroute) < len(bestroute)) {
bestprice = newprice
bestroute = newroute
}
}
return bestprice, nil
}