From e52d80bad360ab0a6a690b48a4ae46361b9f6fb3 Mon Sep 17 00:00:00 2001 From: asolovov Date: Fri, 17 May 2024 20:34:04 +0300 Subject: [PATCH] feat: account event for Core and Account contracts --- Makefile | 5 ++ config/config.go | 4 ++ contracts/core/contract.go | 4 +- events/accountPermissionRevokedCore.go | 87 +++++++++++++++++++++++++ events/accountPermissionsGrantedCore.go | 87 +++++++++++++++++++++++++ events/accountTransfer.go | 84 ++++++++++++++++++++++++ events/events.go | 16 +++++ models/account.go | 21 ++++++ perpsv3.go | 52 ++++++++++++++- 9 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 events/accountPermissionRevokedCore.go create mode 100644 events/accountPermissionsGrantedCore.go create mode 100644 events/accountTransfer.go diff --git a/Makefile b/Makefile index 3da2855..6208a41 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,11 @@ generate-erc7412-mainnet-c: go run ./utils/getAbis/get-abis.go --get-mkdir ./cannon-synthetix/base/pyth_erc7412_wrapper/PythERC7412Wrapper.json ./contracts/ERC7412 abigen --abi=./contracts/PythERC7412Wrapper.json --pkg=ERC7412 --out=./contracts/ERC7412/contract.go +generate-account-mainnet-c: + go run ./utils/getAbis/get-abis.go --get-mkdir ./cannon-synthetix/base/system/AccountProxy.json ./contracts/Account + abigen --abi=./contracts/AccountProxy.json --pkg=Account --out=./contracts/Account/contract.go + + # update Synthetix-Gitbook-v3 subtree update-subtree: git subtree pull --prefix Synthetix-Gitbook-v3 git@github.com:Synthetixio/Synthetix-Gitbook-v3.git en --squash diff --git a/config/config.go b/config/config.go index 175bc7c..5a8733c 100644 --- a/config/config.go +++ b/config/config.go @@ -26,12 +26,14 @@ type ContractAddresses struct { PerpsMarket string ERC7412 string Forwarder string + Account string } // FirstContractBlocks is a part of a config struct with default first block numbers used to filters contract logs type FirstContractBlocks struct { Core uint64 PerpsMarket uint64 + Account uint64 } // GetOptimismGoerliDefaultConfig is used to get default lib config for goerli optimism test net @@ -130,10 +132,12 @@ func GetBaseMainnetDefaultConfig(rpcURL string) *PerpsvConfig { PerpsMarket: "0x0A2AF931eFFd34b81ebcc57E3d3c9B1E1dE1C9Ce", Forwarder: "0xE2C5658cC5C448B48141168f3e475dF8f65A1e3e", ERC7412: "0xEb38e347F24ea04ffA945a475BdD949E0c383A0F", + Account: "0x63f4Dd0434BEB5baeCD27F3778a909278d8cf5b8", }, FirstContractBlocks: &FirstContractBlocks{ Core: 7889212, PerpsMarket: 7889389, + Account: 8394165, }, ConnectionTimeout: time.Second * 30, ReadTimeout: time.Second * 15, diff --git a/contracts/core/contract.go b/contracts/core/contract.go index 9758875..5596a01 100644 --- a/contracts/core/contract.go +++ b/contracts/core/contract.go @@ -7554,7 +7554,7 @@ func (_Core *CoreFilterer) WatchPermissionGranted(opts *bind.WatchOpts, sink cha userRule = append(userRule, userItem) } - logs, sub, err := _Core.contract.WatchLogs(opts, "PermissionGranted", accountIdRule, permissionRule, userRule) + logs, sub, err := _Core.contract.WatchLogs(opts, "PermissionGranted") if err != nil { return nil, err } @@ -7717,7 +7717,7 @@ func (_Core *CoreFilterer) WatchPermissionRevoked(opts *bind.WatchOpts, sink cha userRule = append(userRule, userItem) } - logs, sub, err := _Core.contract.WatchLogs(opts, "PermissionRevoked", accountIdRule, permissionRule, userRule) + logs, sub, err := _Core.contract.WatchLogs(opts, "PermissionRevoked") if err != nil { return nil, err } diff --git a/events/accountPermissionRevokedCore.go b/events/accountPermissionRevokedCore.go new file mode 100644 index 0000000..0b80602 --- /dev/null +++ b/events/accountPermissionRevokedCore.go @@ -0,0 +1,87 @@ +package events + +import ( + "strings" + + "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" +) + +// AccountCorePermissionRevokedSubscription is a struct for listening to all 'PermissionRevoked' contract events and return them as models.PermissionChanged struct +type AccountCorePermissionRevokedSubscription struct { + *basicSubscription + PermissionChangeChan chan *models.PermissionChanged + contractEventChan chan *core.CorePermissionRevoked +} + +func (e *Events) ListenAccountCorePermissionRevoked() (*AccountCorePermissionRevokedSubscription, error) { + revokedChan := make(chan *core.CorePermissionRevoked) + + revokedSub, err := e.core.WatchPermissionRevoked(nil, revokedChan, nil, nil, nil) + if err != nil { + logger.Log().WithField("layer", "Events-AccountCorePermissionRevoked").Errorf("error watch permission revoked: %v", err.Error()) + return nil, errors.GetEventListenErr(err, "AccountCorePermissionRevoked") + } + + accountsSub := newAccountCorePermissionRevokedSubscription(revokedSub, revokedChan) + + go accountsSub.listen(e.core) + + return accountsSub, nil +} + +// newAccountCorePermissionRevokedSubscription is used to get new AccountCorePermissionRevokedSubscription instance +func newAccountCorePermissionRevokedSubscription( + eventSub event.Subscription, + revoked chan *core.CorePermissionRevoked, +) *AccountCorePermissionRevokedSubscription { + return &AccountCorePermissionRevokedSubscription{ + basicSubscription: newBasicSubscription(eventSub), + PermissionChangeChan: make(chan *models.PermissionChanged), + contractEventChan: revoked, + } +} + +// listen is used to run events listen goroutine +func (s *AccountCorePermissionRevokedSubscription) listen(perps *core.Core) { + defer func() { + close(s.PermissionChangeChan) + close(s.contractEventChan) + }() + + for { + select { + case <-s.stop: + return + case err := <-s.eventSub.Err(): + if err != nil { + logger.Log().WithField("layer", "Events-AccountCorePermissionRevoked").Errorf( + "error listening account permission revoked: %v", err.Error(), + ) + s.ErrChan <- err + } + return + case contractEvent := <-s.contractEventChan: + p, err := models.PermissionFromString(strings.TrimRight(string(contractEvent.Permission[:]), string(rune(0)))) + if err != nil { + logger.Log().WithField("layer", "Events-AccountCorePermissionRevoked").Errorf( + "error decode permission %v: %v", string(contractEvent.Permission[:]), err.Error(), + ) + s.ErrChan <- err + continue + } + + change := &models.PermissionChanged{ + AccountID: contractEvent.AccountId, + User: contractEvent.User, + Permission: p, + } + + s.PermissionChangeChan <- change + } + } +} diff --git a/events/accountPermissionsGrantedCore.go b/events/accountPermissionsGrantedCore.go new file mode 100644 index 0000000..b1972cc --- /dev/null +++ b/events/accountPermissionsGrantedCore.go @@ -0,0 +1,87 @@ +package events + +import ( + "strings" + + "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" +) + +// AccountCorePermissionGrantedSubscription is a struct for listening to all 'PermissionGranted' contract events and return them as models.PermissionChanged struct +type AccountCorePermissionGrantedSubscription struct { + *basicSubscription + PermissionChangeChan chan *models.PermissionChanged + contractEventChan chan *core.CorePermissionGranted +} + +func (e *Events) ListenAccountCorePermissionGranted() (*AccountCorePermissionGrantedSubscription, error) { + createdChan := make(chan *core.CorePermissionGranted) + + createdSub, err := e.core.WatchPermissionGranted(nil, createdChan, nil, nil, nil) + if err != nil { + logger.Log().WithField("layer", "Events-AccountCorePermissionGranted").Errorf("error watch permission granted: %v", err.Error()) + return nil, errors.GetEventListenErr(err, "AccountCorePermissionGranted") + } + + accountsSub := newAccountCorePermissionGrantedSubscription(createdSub, createdChan) + + go accountsSub.listen(e.core) + + return accountsSub, nil +} + +// newAccountCorePermissionGrantedSubscription is used to get new AccountCorePermissionGrantedSubscription instance +func newAccountCorePermissionGrantedSubscription( + eventSub event.Subscription, + created chan *core.CorePermissionGranted, +) *AccountCorePermissionGrantedSubscription { + return &AccountCorePermissionGrantedSubscription{ + basicSubscription: newBasicSubscription(eventSub), + PermissionChangeChan: make(chan *models.PermissionChanged), + contractEventChan: created, + } +} + +// listen is used to run events listen goroutine +func (s *AccountCorePermissionGrantedSubscription) listen(perps *core.Core) { + defer func() { + close(s.PermissionChangeChan) + close(s.contractEventChan) + }() + + for { + select { + case <-s.stop: + return + case err := <-s.eventSub.Err(): + if err != nil { + logger.Log().WithField("layer", "Events-AccountCorePermissionGranted").Errorf( + "error listening account permission granted: %v", err.Error(), + ) + s.ErrChan <- err + } + return + case contractEvent := <-s.contractEventChan: + p, err := models.PermissionFromString(strings.TrimRight(string(contractEvent.Permission[:]), string(rune(0)))) + if err != nil { + logger.Log().WithField("layer", "Events-AccountCorePermissionGranted").Errorf( + "error decode permission %v: %v", string(contractEvent.Permission[:]), err.Error(), + ) + s.ErrChan <- err + continue + } + + change := &models.PermissionChanged{ + AccountID: contractEvent.AccountId, + User: contractEvent.User, + Permission: p, + } + + s.PermissionChangeChan <- change + } + } +} diff --git a/events/accountTransfer.go b/events/accountTransfer.go new file mode 100644 index 0000000..9fc3d2b --- /dev/null +++ b/events/accountTransfer.go @@ -0,0 +1,84 @@ +package events + +import ( + "context" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/event" + "github.com/gateway-fm/perpsv3-Go/contracts/Account" + "github.com/gateway-fm/perpsv3-Go/errors" + "github.com/gateway-fm/perpsv3-Go/models" + "github.com/gateway-fm/perpsv3-Go/pkg/logger" + "math/big" +) + +// AccountTransferSubscription is a struct for listening to all 'AccountTransfer' contract events and return them as models.AccountTransfer struct +type AccountTransferSubscription struct { + *basicSubscription + NewAccountChan chan *models.AccountTransfer + contractEventChan chan *Account.AccountTransfer +} + +func (e *Events) ListenAccountTransfer() (*AccountTransferSubscription, error) { + transferChan := make(chan *Account.AccountTransfer) + + transferSub, err := e.account.WatchTransfer(nil, transferChan, nil, nil, nil) + if err != nil { + logger.Log().WithField("layer", "Events-Accounts").Errorf("error watch account transfer: %v", err.Error()) + return nil, errors.GetEventListenErr(err, "AccountTransfer") + } + + accountsSub := newAccountTransferSubscription(transferSub, transferChan) + + go accountsSub.listen(e.rpcClient) + + return accountsSub, nil +} + +// newAccountTransferSubscription is used to get new AccountTransferSubscription instance +func newAccountTransferSubscription( + eventSub event.Subscription, + transfer chan *Account.AccountTransfer, +) *AccountTransferSubscription { + return &AccountTransferSubscription{ + basicSubscription: newBasicSubscription(eventSub), + NewAccountChan: make(chan *models.AccountTransfer), + contractEventChan: transfer, + } +} + +// listen is used to run events listen goroutine +func (s *AccountTransferSubscription) listen(rpcClient *ethclient.Client) { + defer func() { + close(s.NewAccountChan) + close(s.contractEventChan) + }() + + for { + select { + case <-s.stop: + return + case err := <-s.eventSub.Err(): + if err != nil { + logger.Log().WithField("layer", "Events-AccountTransfer").Errorf( + "error listening account transfer info: %v", err.Error(), + ) + s.ErrChan <- err + } + return + case transfer := <-s.contractEventChan: + block, err := rpcClient.HeaderByNumber(context.Background(), big.NewInt(int64(transfer.Raw.BlockNumber))) + time := uint64(0) + if err != nil { + logger.Log().WithField("layer", "Events-AccountTransfer").Warningf( + "error fetching block number %v: %v; transfer event time set to 0 ", + transfer.Raw.BlockNumber, err.Error(), + ) + s.ErrChan <- err + } else { + time = block.Time + } + + s.NewAccountChan <- models.GetAccountTransferFromEvent(transfer, time) + } + } +} diff --git a/events/events.go b/events/events.go index e2f9317..9eb6487 100644 --- a/events/events.go +++ b/events/events.go @@ -1,6 +1,7 @@ package events import ( + "github.com/gateway-fm/perpsv3-Go/contracts/Account" "math/big" "github.com/ethereum/go-ethereum/ethclient" @@ -104,6 +105,18 @@ type IEvents interface { // ListenLiquidationsCore is used to listen to all 'Liquidations' Core contract events and return them as models.CoreLiquidation // struct and return errors on ErrChan chanel ListenLiquidationsCore() (*LiquidationsCoreSubscription, error) + + // ListenAccountTransfer is used to listen to all 'Transfer' Account contract events and return them as models.AccountTransfer + // struct and return errors on ErrChan chanel + ListenAccountTransfer() (*AccountTransferSubscription, error) + + // ListenAccountCorePermissionRevoked is used to listen to all 'PermissionRevoked' Core contract events and return them as models.PermissionChanged + // struct and return errors on ErrChan chanel + ListenAccountCorePermissionRevoked() (*AccountCorePermissionRevokedSubscription, error) + + // ListenAccountCorePermissionGranted is used to listen to all 'PermissionGranted' Core contract events and return them as models.PermissionChanged + // struct and return errors on ErrChan chanel + ListenAccountCorePermissionGranted() (*AccountCorePermissionGrantedSubscription, error) } // Events implements IEvents interface @@ -111,6 +124,7 @@ type Events struct { rpcClient *ethclient.Client core *core.Core perpsMarket *perpsMarket.PerpsMarket + account *Account.Account } // NewEvents is used to create new Events instance that implements IEvents interface @@ -118,11 +132,13 @@ func NewEvents( client *ethclient.Client, core *core.Core, perpsMarket *perpsMarket.PerpsMarket, + account *Account.Account, ) IEvents { return &Events{ rpcClient: client, core: core, perpsMarket: perpsMarket, + account: account, } } diff --git a/models/account.go b/models/account.go index efade74..2e11a6c 100644 --- a/models/account.go +++ b/models/account.go @@ -1,6 +1,7 @@ package models import ( + AccountContract "github.com/gateway-fm/perpsv3-Go/contracts/Account" "github.com/gateway-fm/perpsv3-Go/contracts/core" "math/big" @@ -37,6 +38,26 @@ type AccountCollateral struct { TotalLocked *big.Int } +type AccountTransfer struct { + From common.Address + To common.Address + TokenID *big.Int + BlockNumber uint64 + BlockTimestamp uint64 +} + +func GetAccountTransferFromEvent(event *AccountContract.AccountTransfer, time uint64) *AccountTransfer { + t := &AccountTransfer{} + + t.From = event.From + t.To = event.To + t.TokenID = event.TokenId + t.BlockNumber = event.Raw.BlockNumber + t.BlockTimestamp = time + + return t +} + func GetAccountCollateralFromContract(res struct { TotalDeposited *big.Int TotalAssigned *big.Int diff --git a/perpsv3.go b/perpsv3.go index f90181e..5323513 100644 --- a/perpsv3.go +++ b/perpsv3.go @@ -1,6 +1,7 @@ package perpsv3_Go import ( + "github.com/gateway-fm/perpsv3-Go/contracts/Account" "math/big" "github.com/ethereum/go-ethereum/common" @@ -221,6 +222,18 @@ type IPerpsv3 interface { // struct and return errors on ErrChan chanel ListenLiquidationsCore() (*events.LiquidationsCoreSubscription, error) + // ListenAccountTransfer is used to listen to all 'Transfer' Account contract events and return them as models.AccountTransfer + // struct and return errors on ErrChan chanel + ListenAccountTransfer() (*events.AccountTransferSubscription, error) + + // ListenAccountCorePermissionRevoked is used to listen to all 'PermissionRevoked' Core contract events and return them as models.PermissionChanged + // struct and return errors on ErrChan chanel + ListenAccountCorePermissionRevoked() (*events.AccountCorePermissionRevokedSubscription, error) + + // ListenAccountCorePermissionGranted is used to listen to all 'PermissionGranted' Core contract events and return them as models.PermissionChanged + // struct and return errors on ErrChan chanel + ListenAccountCorePermissionGranted() (*events.AccountCorePermissionGrantedSubscription, 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) @@ -539,6 +552,18 @@ func (p *Perpsv3) ListenLiquidationsCore() (*events.LiquidationsCoreSubscription return p.events.ListenLiquidationsCore() } +func (p *Perpsv3) ListenAccountTransfer() (*events.AccountTransferSubscription, error) { + return p.events.ListenAccountTransfer() +} + +func (p *Perpsv3) ListenAccountCorePermissionRevoked() (*events.AccountCorePermissionRevokedSubscription, error) { + return p.events.ListenAccountCorePermissionRevoked() +} + +func (p *Perpsv3) ListenAccountCorePermissionGranted() (*events.AccountCorePermissionGrantedSubscription, error) { + return p.events.ListenAccountCorePermissionGranted() +} + func (p *Perpsv3) GetPosition(accountID *big.Int, marketID *big.Int) (*models.Position, error) { return p.service.GetPosition(accountID, marketID) } @@ -679,13 +704,18 @@ func (p *Perpsv3) init() error { return err } + accountContract, err := p.getAccountContract() + if err != nil { + return err + } + srv, err := services.NewService(rpcClient, p.config, coreContact, perpsMarketContract) if err != nil { return err } p.service = srv - p.events = events.NewEvents(rpcClient, coreContact, perpsMarketContract) + p.events = events.NewEvents(rpcClient, coreContact, perpsMarketContract, accountContract) return nil } @@ -711,6 +741,26 @@ func (p *Perpsv3) getCoreContract() (*core.Core, error) { } } +func (p *Perpsv3) getAccountContract() (*Account.Account, error) { + if p.config.ContractAddresses.Account != "" { + addr, err := getAddr(p.config.ContractAddresses.Account, "account") + if err != nil { + return nil, err + } + + contract, err := Account.NewAccount(addr, p.rpcClient) + if err != nil { + logger.Log().WithField("layer", "Init").Errorf("error getting account contract: %v", err.Error()) + return nil, errors.GetInitContractErr(err) + } + + return contract, nil + } else { + logger.Log().WithField("layer", "Init").Errorf("no account contract address") + return nil, errors.BlankContractAddrErr + } +} + // getGoerliPerpsMarket is used to get perps market contract instance func (p *Perpsv3) getPerpsMarket() (*perpsMarket.PerpsMarket, error) { if p.config.ContractAddresses.PerpsMarket != "" {