From 4f3d1abd0dde8c7ba3fa17eb0d0e60bf5a94bcaf Mon Sep 17 00:00:00 2001 From: asolovov Date: Tue, 16 Jan 2024 22:06:10 +0300 Subject: [PATCH] SNX-31: add Core contract usd minted event --- events/events.go | 4 ++ events/usdMinted.go | 83 +++++++++++++++++++++++++++++++++++++++ models/usdMinted.go | 39 +++++++++++++++++++ perpsv3.go | 20 ++++++++++ services/pools.go | 94 +++++++++++++++++++++++++++++++++++++++++++++ services/service.go | 17 ++++++++ 6 files changed, 257 insertions(+) create mode 100644 events/usdMinted.go create mode 100644 models/usdMinted.go create mode 100644 services/pools.go diff --git a/events/events.go b/events/events.go index abbcce2..414f805 100644 --- a/events/events.go +++ b/events/events.go @@ -48,6 +48,10 @@ type IEvents interface { // ListenAccountPermissionGranted is used to listen to all 'PermissionGranted' contract events and return them as models.PermissionChanged // struct and return errors on ErrChan chanel ListenAccountPermissionGranted() (*AccountPermissionGrantedSubscription, error) + + // ListenUSDMinted is used to listen to all 'USDMinted' Core contract events and return them as models.USDMinted + // struct and return errors on ErrChan chanel + ListenUSDMinted() (*USDMintedSubscription, error) } // Events implements IEvents interface diff --git a/events/usdMinted.go b/events/usdMinted.go new file mode 100644 index 0000000..a7b0b2f --- /dev/null +++ b/events/usdMinted.go @@ -0,0 +1,83 @@ +package events + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/event" + + "github.com/gateway-fm/perpsv3-Go/contracts/core" + "github.com/gateway-fm/perpsv3-Go/errors" + "github.com/gateway-fm/perpsv3-Go/models" + "github.com/gateway-fm/perpsv3-Go/pkg/logger" +) + +// USDMintedSubscription is a struct for listening to all 'PositionLiquidated' contract events and return them as models.USDMinted struct +type USDMintedSubscription struct { + *basicSubscription + USDMintedsChan chan *models.USDMinted + contractEventChan chan *core.CoreUsdMinted +} + +func (e *Events) ListenUSDMinted() (*USDMintedSubscription, error) { + contractEventChan := make(chan *core.CoreUsdMinted) + + contractSub, err := e.core.WatchUsdMinted(nil, contractEventChan, nil, nil, nil) + if err != nil { + logger.Log().WithField("layer", "Events-ListenUSDMinteds").Errorf("error watch usd minted: %v", err.Error()) + return nil, errors.GetEventListenErr(err, "USDMinted") + } + + orderSub := newUSDMintedSubscription(contractSub, contractEventChan) + + go orderSub.listen(e.rpcClient) + + return orderSub, nil +} + +// newUSDMintedSubscription is used to create new USDMintedSubscription instance +func newUSDMintedSubscription(eventSub event.Subscription, contractEventChan chan *core.CoreUsdMinted) *USDMintedSubscription { + return &USDMintedSubscription{ + basicSubscription: newBasicSubscription(eventSub), + contractEventChan: contractEventChan, + USDMintedsChan: make(chan *models.USDMinted), + } +} + +// listen is used to run a goroutine +func (s *USDMintedSubscription) listen(rpcClient *ethclient.Client) { + defer func() { + close(s.USDMintedsChan) + close(s.contractEventChan) + }() + + for { + select { + case <-s.stop: + return + case err := <-s.eventSub.Err(): + if err != nil { + logger.Log().WithField("layer", "Events-PositionLiquidated").Errorf("error listening position liquidated: %v", err.Error()) + s.ErrChan <- err + } + return + case positionLiquidated := <-s.contractEventChan: + block, err := rpcClient.HeaderByNumber(context.Background(), big.NewInt(int64(positionLiquidated.Raw.BlockNumber))) + time := uint64(0) + if err != nil { + logger.Log().WithField("layer", "Events-PositionLiquidated").Warningf( + "error fetching block number %v: %v; order event time set to 0 ", + positionLiquidated.Raw.BlockNumber, err.Error(), + ) + s.ErrChan <- err + } else { + time = block.Time + } + + order := models.GetUSDMintedFromEvent(positionLiquidated, time) + + s.USDMintedsChan <- order + } + } +} diff --git a/models/usdMinted.go b/models/usdMinted.go new file mode 100644 index 0000000..44906f0 --- /dev/null +++ b/models/usdMinted.go @@ -0,0 +1,39 @@ +package models + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/gateway-fm/perpsv3-Go/contracts/core" + "github.com/gateway-fm/perpsv3-Go/pkg/logger" +) + +// USDMinted is a `usdMinted` Core smart-contract event struct +type USDMinted struct { + AccountId *big.Int + PoolId *big.Int + CollateralType common.Address + Amount *big.Int + Sender common.Address + BlockNumber uint64 + BlockTimestamp uint64 +} + +// GetUSDMintedFromEvent is used to get USDMinted struct from given contract event +func GetUSDMintedFromEvent(event *core.CoreUsdMinted, time uint64) *USDMinted { + if event == nil { + logger.Log().WithField("layer", "Models-USDMinted").Warning("nil event received") + return &USDMinted{} + } + + return &USDMinted{ + AccountId: event.AccountId, + PoolId: event.PoolId, + CollateralType: event.CollateralType, + Amount: event.Amount, + Sender: event.Sender, + BlockNumber: event.Raw.BlockNumber, + BlockTimestamp: time, + } +} diff --git a/perpsv3.go b/perpsv3.go index c8ff76b..e3e8dcc 100644 --- a/perpsv3.go +++ b/perpsv3.go @@ -71,6 +71,10 @@ type IPerpsv3 interface { // limit. For most public RPC providers the value for limit is 20 000 blocks RetrieveAccountLiquidationsLimit(limit uint64) ([]*models.AccountLiquidated, error) + // RetrieveUSDMintedLimit is used to get all `usdMinted` events from the Core contract with given block search + // limit. For most public RPC providers the value for limit is 20 000 blocks + RetrieveUSDMintedLimit(limit uint64) ([]*models.USDMinted, error) + // ListenTrades is used to subscribe on the contract "OrderSettled" event. The goroutine will return events on the // TradesChan chanel and errors on the ErrChan chanel. // To close the subscription use events.TradeSubscription `Close` function @@ -112,6 +116,10 @@ type IPerpsv3 interface { // struct and return errors on ErrChan chanel ListenAccountPermissionGranted() (*events.AccountPermissionGrantedSubscription, error) + // ListenUSDMinted is used to listen to all 'USDMinted' Core contract events and return them as models.USDMinted + // struct and return errors on ErrChan chanel + ListenUSDMinted() (*events.USDMintedSubscription, error) + // GetPosition is used to get position data struct from latest block with given params // Function can return contract error if market ID is invalid GetPosition(accountID *big.Int, marketID *big.Int) (*models.Position, error) @@ -248,6 +256,10 @@ func (p *Perpsv3) RetrieveAccountLiquidationsLimit(limit uint64) ([]*models.Acco return p.service.RetrieveAccountLiquidationsLimit(limit) } +func (p *Perpsv3) RetrieveUSDMintedLimit(limit uint64) ([]*models.USDMinted, error) { + return p.service.RetrieveUSDMintedLimit(limit) +} + func (p *Perpsv3) ListenTrades() (*events.TradeSubscription, error) { return p.events.ListenTrades() } @@ -284,6 +296,10 @@ func (p *Perpsv3) ListenAccountPermissionGranted() (*events.AccountPermissionGra return p.events.ListenAccountPermissionGranted() } +func (p *Perpsv3) ListenUSDMinted() (*events.USDMintedSubscription, error) { + return p.events.ListenUSDMinted() +} + func (p *Perpsv3) GetPosition(accountID *big.Int, marketID *big.Int) (*models.Position, error) { return p.service.GetPosition(accountID, marketID) } @@ -453,3 +469,7 @@ func getAddr(addr string, name string) (common.Address, error) { // // return lib, nil //} +// } +// +// return lib, nil +//} diff --git a/services/pools.go b/services/pools.go new file mode 100644 index 0000000..cdcff80 --- /dev/null +++ b/services/pools.go @@ -0,0 +1,94 @@ +package services + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + + "github.com/gateway-fm/perpsv3-Go/contracts/core" + "github.com/gateway-fm/perpsv3-Go/errors" + "github.com/gateway-fm/perpsv3-Go/models" + "github.com/gateway-fm/perpsv3-Go/pkg/logger" +) + +func (s *Service) RetrieveUSDMintedLimit(limit uint64) ([]*models.USDMinted, error) { + iterations, last, err := s.getIterationsForLimitQuery(limit) + if err != nil { + return nil, err + } + + var mints []*models.USDMinted + + logger.Log().WithField("layer", "Service-RetrieveUSDMintedLimit").Infof( + "fetching USDMinted with limit: %v to block: %v total iterations: %v...", + limit, last, iterations, + ) + + fromBlock := s.coreFirstBlock + toBlock := fromBlock + limit + for i := uint64(1); i <= iterations; i++ { + if i%10 == 0 || i == iterations { + logger.Log().WithField("layer", "Service-RetrieveUSDMintedLimit").Infof("-- iteration %v", i) + } + opts := s.getFilterOptsCore(fromBlock, &toBlock) + + res, err := s.retrieveUSDMinted(opts) + if err != nil { + return nil, err + } + + mints = append(mints, res...) + + fromBlock = toBlock + 1 + + if i == iterations-1 { + toBlock = last + } else { + toBlock = fromBlock + limit + } + } + + logger.Log().WithField("layer", "Service-RetrieveLiquidationsLimit").Infof("task completed successfully") + + return mints, nil +} + +func (s *Service) retrieveUSDMinted(opts *bind.FilterOpts) ([]*models.USDMinted, error) { + iterator, err := s.core.FilterUsdMinted(opts, nil, nil, nil) + if err != nil { + logger.Log().WithField("layer", "Service-RetrieveUSDMintedLimit").Errorf("error get iterator: %v", err.Error()) + return nil, errors.GetFilterErr(err, "core") + } + + var mints []*models.USDMinted + + for iterator.Next() { + if iterator.Error() != nil { + logger.Log().WithField("layer", "Service-RetrieveUSDMintedLimit").Errorf("iterator error: %v", iterator.Error().Error()) + return nil, errors.GetFilterErr(iterator.Error(), "core") + } + + mint, err := s.getUSDMinted(iterator.Event, iterator.Event.Raw.BlockNumber) + if err != nil { + return nil, err + } + + mints = append(mints, mint) + } + + return mints, nil +} + +// getLiquidation is used to get models.Liquidation from given event and block number +func (s *Service) getUSDMinted(event *core.CoreUsdMinted, blockN uint64) (*models.USDMinted, error) { + block, err := s.rpcClient.HeaderByNumber(context.Background(), big.NewInt(int64(blockN))) + if err != nil { + logger.Log().WithField("layer", "Service-RetrieveUSDMintedLimit").Errorf( + "get block:%v by number error: %v", blockN, err.Error(), + ) + return nil, errors.GetRPCProviderErr(err, "HeaderByNumber") + } + + return models.GetUSDMintedFromEvent(event, block.Time), nil +} diff --git a/services/service.go b/services/service.go index 63450d2..e991a78 100644 --- a/services/service.go +++ b/services/service.go @@ -62,6 +62,10 @@ type IService interface { // limit. For most public RPC providers the value for limit is 20 000 blocks RetrieveAccountLiquidationsLimit(limit uint64) ([]*models.AccountLiquidated, error) + // RetrieveUSDMintedLimit is used to get all `usdMinted` events from the Core contract with given block search + // limit. For most public RPC providers the value for limit is 20 000 blocks + RetrieveUSDMintedLimit(limit uint64) ([]*models.USDMinted, error) + // GetPosition is used to get "Position" data struct from the latest block from the perps market with given data GetPosition(accountID *big.Int, marketID *big.Int) (*models.Position, error) @@ -203,3 +207,16 @@ func (s *Service) getFilterOptsPerpsMarket(fromBlock uint64, toBLock *uint64) *b Context: context.Background(), } } + +// getFilterOptsPerpsMarket is used to get options for event filtering on perps market contract +func (s *Service) getFilterOptsCore(fromBlock uint64, toBLock *uint64) *bind.FilterOpts { + if fromBlock == 0 { + fromBlock = s.coreFirstBlock + } + + return &bind.FilterOpts{ + Start: fromBlock, + End: toBLock, + Context: context.Background(), + } +}