diff --git a/cmd/tradelogs/main.go b/cmd/tradelogs/main.go index 886b640..93c72e5 100644 --- a/cmd/tradelogs/main.go +++ b/cmd/tradelogs/main.go @@ -10,7 +10,9 @@ import ( "github.com/KyberNetwork/tradelogs/internal/dbutil" "github.com/KyberNetwork/tradelogs/internal/evmlistenerclient" "github.com/KyberNetwork/tradelogs/internal/parser/kyberswap" + "github.com/KyberNetwork/tradelogs/internal/parser/tokenlon" "github.com/KyberNetwork/tradelogs/internal/parser/zxotc" + "github.com/KyberNetwork/tradelogs/internal/parser/zxrfq" "github.com/KyberNetwork/tradelogs/internal/server" "github.com/KyberNetwork/tradelogs/internal/storage" "github.com/KyberNetwork/tradelogs/internal/worker" @@ -64,6 +66,8 @@ func run(c *cli.Context) error { w, err := worker.New(l, s, listener, kyberswap.MustNewParser(), zxotc.MustNewParser(), + zxrfq.MustNewParser(), + tokenlon.MustNewParser(), ) if err != nil { l.Errorw("Error while init worker") diff --git a/internal/parser/kyberswap/kyberswap_test.go b/internal/parser/kyberswap/kyberswap_test.go index 6076586..bddba8c 100644 --- a/internal/parser/kyberswap/kyberswap_test.go +++ b/internal/parser/kyberswap/kyberswap_test.go @@ -16,6 +16,8 @@ import ( func TestFetchEvent(t *testing.T) { t.Skip() + p := MustNewParser() + require.Equal(t, p.abi.Events[SwappedEvent].ID, common.HexToHash("0xd6d4f5681c246c9f42c203e287975af1601f8df8035a9251f79aab5c8f09e2f8")) client, err := ethclient.Dial("https://mainnet.infura.io/v3/") require.NoError(t, err) logs, err := client.FilterLogs(context.Background(), ethereum.FilterQuery{ diff --git a/internal/parser/tokenlon/abi.json b/internal/parser/tokenlon/abi.json new file mode 100644 index 0000000..5b689ee --- /dev/null +++ b/internal/parser/tokenlon/abi.json @@ -0,0 +1,81 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "source", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "transactionHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "userAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "takerAssetAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "takerAssetAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "makerAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "makerAssetAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "makerAssetAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiverAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "settleAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "feeFactor", + "type": "uint16" + } + ], + "name": "FillOrder", + "type": "event" + } +] diff --git a/internal/parser/tokenlon/gen.sh b/internal/parser/tokenlon/gen.sh new file mode 100755 index 0000000..cd0d670 --- /dev/null +++ b/internal/parser/tokenlon/gen.sh @@ -0,0 +1 @@ +abigen --abi=abi.json --pkg=tokenlon --out=tokenlon.go -type FillOrder \ No newline at end of file diff --git a/internal/parser/tokenlon/parser.go b/internal/parser/tokenlon/parser.go new file mode 100644 index 0000000..8e1b288 --- /dev/null +++ b/internal/parser/tokenlon/parser.go @@ -0,0 +1,73 @@ +package tokenlon + +import ( + "errors" + + "github.com/KyberNetwork/tradelogs/internal/storage" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + FillOrderEvent = "FillOrder" +) + +var ErrInvalidRFQTopic = errors.New("invalid RfqFilled topic") + +type Parser struct { + abi *abi.ABI + ps *FillOrderFilterer + eventHash string +} + +func MustNewParser() *Parser { + ps, err := NewFillOrderFilterer(common.Address{}, nil) + if err != nil { + panic(err) + } + ab, err := FillOrderMetaData.GetAbi() + if err != nil { + panic(err) + } + event, ok := ab.Events[FillOrderEvent] + if !ok { + panic("no such event: FillOrder") + } + return &Parser{ + ps: ps, + abi: ab, + eventHash: event.ID.String(), + } +} + +func (p *Parser) Topics() []string { + return []string{ + p.eventHash, + } +} + +func (p *Parser) Parse(log types.Log, blockTime uint64) (storage.TradeLog, error) { + if len(log.Topics) > 0 && log.Topics[0].Hex() != p.eventHash { + return storage.TradeLog{}, ErrInvalidRFQTopic + } + o, err := p.ps.ParseFillOrder(log) + if err != nil { + return storage.TradeLog{}, err + } + res := storage.TradeLog{ + OrderHash: common.Hash(o.OrderHash).String(), + Maker: o.MakerAddr.Hex(), + Taker: o.ReceiverAddr.Hex(), + MakerToken: o.MakerAssetAddr.Hex(), + TakerToken: o.TakerAssetAddr.Hex(), + MakerTokenAmount: o.MakerAssetAmount.String(), + TakerTokenAmount: o.SettleAmount.String(), + ContractAddress: o.Raw.Address.String(), + BlockNumber: o.Raw.BlockNumber, + TxHash: o.Raw.TxHash.String(), + LogIndex: uint64(o.Raw.Index), + Timestamp: blockTime * 1000, + } + return res, nil +} diff --git a/internal/parser/tokenlon/tokenlon.go b/internal/parser/tokenlon/tokenlon.go new file mode 100644 index 0000000..ca69d8c --- /dev/null +++ b/internal/parser/tokenlon/tokenlon.go @@ -0,0 +1,352 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package tokenlon + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// FillOrderMetaData contains all meta data concerning the FillOrder contract. +var FillOrderMetaData = &bind.MetaData{ + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"string\",\"name\":\"source\",\"type\":\"string\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"transactionHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"userAddr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"takerAssetAddr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"takerAssetAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"makerAddr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"makerAssetAddr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"makerAssetAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiverAddr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"settleAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"feeFactor\",\"type\":\"uint16\"}],\"name\":\"FillOrder\",\"type\":\"event\"}]", +} + +// FillOrderABI is the input ABI used to generate the binding from. +// Deprecated: Use FillOrderMetaData.ABI instead. +var FillOrderABI = FillOrderMetaData.ABI + +// FillOrder is an auto generated Go binding around an Ethereum contract. +type FillOrder struct { + FillOrderCaller // Read-only binding to the contract + FillOrderTransactor // Write-only binding to the contract + FillOrderFilterer // Log filterer for contract events +} + +// FillOrderCaller is an auto generated read-only Go binding around an Ethereum contract. +type FillOrderCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// FillOrderTransactor is an auto generated write-only Go binding around an Ethereum contract. +type FillOrderTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// FillOrderFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type FillOrderFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// FillOrderSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type FillOrderSession struct { + Contract *FillOrder // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// FillOrderCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type FillOrderCallerSession struct { + Contract *FillOrderCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// FillOrderTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type FillOrderTransactorSession struct { + Contract *FillOrderTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// FillOrderRaw is an auto generated low-level Go binding around an Ethereum contract. +type FillOrderRaw struct { + Contract *FillOrder // Generic contract binding to access the raw methods on +} + +// FillOrderCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type FillOrderCallerRaw struct { + Contract *FillOrderCaller // Generic read-only contract binding to access the raw methods on +} + +// FillOrderTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type FillOrderTransactorRaw struct { + Contract *FillOrderTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewFillOrder creates a new instance of FillOrder, bound to a specific deployed contract. +func NewFillOrder(address common.Address, backend bind.ContractBackend) (*FillOrder, error) { + contract, err := bindFillOrder(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &FillOrder{FillOrderCaller: FillOrderCaller{contract: contract}, FillOrderTransactor: FillOrderTransactor{contract: contract}, FillOrderFilterer: FillOrderFilterer{contract: contract}}, nil +} + +// NewFillOrderCaller creates a new read-only instance of FillOrder, bound to a specific deployed contract. +func NewFillOrderCaller(address common.Address, caller bind.ContractCaller) (*FillOrderCaller, error) { + contract, err := bindFillOrder(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &FillOrderCaller{contract: contract}, nil +} + +// NewFillOrderTransactor creates a new write-only instance of FillOrder, bound to a specific deployed contract. +func NewFillOrderTransactor(address common.Address, transactor bind.ContractTransactor) (*FillOrderTransactor, error) { + contract, err := bindFillOrder(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &FillOrderTransactor{contract: contract}, nil +} + +// NewFillOrderFilterer creates a new log filterer instance of FillOrder, bound to a specific deployed contract. +func NewFillOrderFilterer(address common.Address, filterer bind.ContractFilterer) (*FillOrderFilterer, error) { + contract, err := bindFillOrder(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &FillOrderFilterer{contract: contract}, nil +} + +// bindFillOrder binds a generic wrapper to an already deployed contract. +func bindFillOrder(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := FillOrderMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_FillOrder *FillOrderRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _FillOrder.Contract.FillOrderCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_FillOrder *FillOrderRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _FillOrder.Contract.FillOrderTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_FillOrder *FillOrderRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _FillOrder.Contract.FillOrderTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_FillOrder *FillOrderCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _FillOrder.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_FillOrder *FillOrderTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _FillOrder.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_FillOrder *FillOrderTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _FillOrder.Contract.contract.Transact(opts, method, params...) +} + +// FillOrderFillOrderIterator is returned from FilterFillOrder and is used to iterate over the raw logs and unpacked data for FillOrder events raised by the FillOrder contract. +type FillOrderFillOrderIterator struct { + Event *FillOrderFillOrder // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *FillOrderFillOrderIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(FillOrderFillOrder) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(FillOrderFillOrder) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *FillOrderFillOrderIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *FillOrderFillOrderIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// FillOrderFillOrder represents a FillOrder event raised by the FillOrder contract. +type FillOrderFillOrder struct { + Source string + TransactionHash [32]byte + OrderHash [32]byte + UserAddr common.Address + TakerAssetAddr common.Address + TakerAssetAmount *big.Int + MakerAddr common.Address + MakerAssetAddr common.Address + MakerAssetAmount *big.Int + ReceiverAddr common.Address + SettleAmount *big.Int + FeeFactor uint16 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterFillOrder is a free log retrieval operation binding the contract event 0x75d58426b26ab641a6a6a46f12fe35e17c570a1cd264c7248a73d90e3a8682ff. +// +// Solidity: event FillOrder(string source, bytes32 indexed transactionHash, bytes32 indexed orderHash, address indexed userAddr, address takerAssetAddr, uint256 takerAssetAmount, address makerAddr, address makerAssetAddr, uint256 makerAssetAmount, address receiverAddr, uint256 settleAmount, uint16 feeFactor) +func (_FillOrder *FillOrderFilterer) FilterFillOrder(opts *bind.FilterOpts, transactionHash [][32]byte, orderHash [][32]byte, userAddr []common.Address) (*FillOrderFillOrderIterator, error) { + + var transactionHashRule []interface{} + for _, transactionHashItem := range transactionHash { + transactionHashRule = append(transactionHashRule, transactionHashItem) + } + var orderHashRule []interface{} + for _, orderHashItem := range orderHash { + orderHashRule = append(orderHashRule, orderHashItem) + } + var userAddrRule []interface{} + for _, userAddrItem := range userAddr { + userAddrRule = append(userAddrRule, userAddrItem) + } + + logs, sub, err := _FillOrder.contract.FilterLogs(opts, "FillOrder", transactionHashRule, orderHashRule, userAddrRule) + if err != nil { + return nil, err + } + return &FillOrderFillOrderIterator{contract: _FillOrder.contract, event: "FillOrder", logs: logs, sub: sub}, nil +} + +// WatchFillOrder is a free log subscription operation binding the contract event 0x75d58426b26ab641a6a6a46f12fe35e17c570a1cd264c7248a73d90e3a8682ff. +// +// Solidity: event FillOrder(string source, bytes32 indexed transactionHash, bytes32 indexed orderHash, address indexed userAddr, address takerAssetAddr, uint256 takerAssetAmount, address makerAddr, address makerAssetAddr, uint256 makerAssetAmount, address receiverAddr, uint256 settleAmount, uint16 feeFactor) +func (_FillOrder *FillOrderFilterer) WatchFillOrder(opts *bind.WatchOpts, sink chan<- *FillOrderFillOrder, transactionHash [][32]byte, orderHash [][32]byte, userAddr []common.Address) (event.Subscription, error) { + + var transactionHashRule []interface{} + for _, transactionHashItem := range transactionHash { + transactionHashRule = append(transactionHashRule, transactionHashItem) + } + var orderHashRule []interface{} + for _, orderHashItem := range orderHash { + orderHashRule = append(orderHashRule, orderHashItem) + } + var userAddrRule []interface{} + for _, userAddrItem := range userAddr { + userAddrRule = append(userAddrRule, userAddrItem) + } + + logs, sub, err := _FillOrder.contract.WatchLogs(opts, "FillOrder", transactionHashRule, orderHashRule, userAddrRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(FillOrderFillOrder) + if err := _FillOrder.contract.UnpackLog(event, "FillOrder", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseFillOrder is a log parse operation binding the contract event 0x75d58426b26ab641a6a6a46f12fe35e17c570a1cd264c7248a73d90e3a8682ff. +// +// Solidity: event FillOrder(string source, bytes32 indexed transactionHash, bytes32 indexed orderHash, address indexed userAddr, address takerAssetAddr, uint256 takerAssetAmount, address makerAddr, address makerAssetAddr, uint256 makerAssetAmount, address receiverAddr, uint256 settleAmount, uint16 feeFactor) +func (_FillOrder *FillOrderFilterer) ParseFillOrder(log types.Log) (*FillOrderFillOrder, error) { + event := new(FillOrderFillOrder) + if err := _FillOrder.contract.UnpackLog(event, "FillOrder", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/internal/parser/tokenlon/tokenlon_test.go b/internal/parser/tokenlon/tokenlon_test.go new file mode 100644 index 0000000..fc63222 --- /dev/null +++ b/internal/parser/tokenlon/tokenlon_test.go @@ -0,0 +1,58 @@ +package tokenlon + +import ( + "context" + "encoding/json" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +func TestFetchEvent(t *testing.T) { + // t.Skip() + p := MustNewParser() + require.Equal(t, p.abi.Events[FillOrderEvent].ID, common.HexToHash("0x75d58426b26ab641a6a6a46f12fe35e17c570a1cd264c7248a73d90e3a8682ff")) + client, err := ethclient.Dial("https://mainnet.infura.io/v3/5fa0422fcae6466d943ac5b1d4b8078e") + require.NoError(t, err) + logs, err := client.FilterLogs(context.Background(), ethereum.FilterQuery{ + BlockHash: nil, + FromBlock: big.NewInt(16381232), + ToBlock: big.NewInt(16381232), + Addresses: nil, + Topics: [][]common.Hash{ + { + common.HexToHash("0x75d58426b26ab641a6a6a46f12fe35e17c570a1cd264c7248a73d90e3a8682ff"), + }, + }, + }) + require.NoError(t, err) + d, _ := json.Marshal(logs) + t.Log(string(d)) +} + +func TestParseEvent(t *testing.T) { + eventRaw := `{ + "address":"0xfd6c2d2499b1331101726a8ac68ccc9da3fab54f", + "topics":["0x75d58426b26ab641a6a6a46f12fe35e17c570a1cd264c7248a73d90e3a8682ff","0x50cc97dc89796148c9975215413df35761ef27f9c59a222ec5eccde85bf35dd3","0xd2b210488c13475e08babd177b7eeaa5d5431edda7835fe9422f2085499a8454","0x000000000000000000000000ac06105579719ac8c22935be712ab17a3d51b415"], + "data":"0x0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000016f5854c184970000000000000000000000000000b3c839dbde6b96d37c56ee4f9dad3390d49310aa000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000083081cc6b000000000000000000000000ac06105579719ac8c22935be712ab17a3d51b415000000000000000000000000000000000000000000000000000000082be50bb6000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000065246512076310000000000000000000000000000000000000000000000000000", + "blockNumber":"0xf9f530", + "transactionHash":"0xf4423faa840a957508b0ddd031f001c69ad6453709711912e3d34b536acbaa89", + "transactionIndex":"0x5d", + "blockHash":"0xf09bfddb6c3bfa577c2ba95133fb0460faa474bbd5242bb816e181f7d662d79c", + "logIndex":"0x118", + "removed":false + }` + event := types.Log{} + err := json.Unmarshal([]byte(eventRaw), &event) + require.NoError(t, err) + p := MustNewParser() + log, err := p.Parse(event, uint64(time.Now().Unix())) + require.NoError(t, err) + t.Log(log) +} diff --git a/internal/parser/zxotc/zxotc_test.go b/internal/parser/zxotc/zxotc_test.go index 4149103..34ddb6c 100644 --- a/internal/parser/zxotc/zxotc_test.go +++ b/internal/parser/zxotc/zxotc_test.go @@ -16,6 +16,8 @@ import ( func TestFetchEvent(t *testing.T) { t.Skip() + p := MustNewParser() + require.Equal(t, p.abi.Events[OtcOrderFilledEvent].ID, common.HexToHash("0xac75f773e3a92f1a02b12134d65e1f47f8a14eabe4eaf1e24624918e6a8b269f")) client, err := ethclient.Dial("https://mainnet.infura.io/v3/") require.NoError(t, err) logs, err := client.FilterLogs(context.Background(), ethereum.FilterQuery{ diff --git a/internal/parser/zxrfq/abi.json b/internal/parser/zxrfq/abi.json new file mode 100644 index 0000000..79b624c --- /dev/null +++ b/internal/parser/zxrfq/abi.json @@ -0,0 +1,57 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "makerToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "takerToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "takerTokenFilledAmount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "makerTokenFilledAmount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "pool", + "type": "bytes32" + } + ], + "name": "RfqOrderFilled", + "type": "event" + } +] diff --git a/internal/parser/zxrfq/gen.sh b/internal/parser/zxrfq/gen.sh new file mode 100755 index 0000000..966e734 --- /dev/null +++ b/internal/parser/zxrfq/gen.sh @@ -0,0 +1 @@ +abigen --abi=abi.json --pkg=zxrfq --out=zxrfq.go -type RfqOrderFilled \ No newline at end of file diff --git a/internal/parser/zxrfq/parser.go b/internal/parser/zxrfq/parser.go new file mode 100644 index 0000000..662b07d --- /dev/null +++ b/internal/parser/zxrfq/parser.go @@ -0,0 +1,73 @@ +package zxrfq + +import ( + "errors" + + "github.com/KyberNetwork/tradelogs/internal/storage" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + RfqOrderFilledEvent = "RfqOrderFilled" +) + +var ErrInvalidRFQTopic = errors.New("invalid RfqFilled topic") + +type Parser struct { + abi *abi.ABI + ps *RfqOrderFilledFilterer + eventHash string +} + +func MustNewParser() *Parser { + ps, err := NewRfqOrderFilledFilterer(common.Address{}, nil) + if err != nil { + panic(err) + } + ab, err := RfqOrderFilledMetaData.GetAbi() + if err != nil { + panic(err) + } + event, ok := ab.Events[RfqOrderFilledEvent] + if !ok { + panic("no such event: RfqOrderFilled") + } + return &Parser{ + ps: ps, + abi: ab, + eventHash: event.ID.String(), + } +} + +func (p *Parser) Topics() []string { + return []string{ + p.eventHash, + } +} + +func (p *Parser) Parse(log types.Log, blockTime uint64) (storage.TradeLog, error) { + if len(log.Topics) > 0 && log.Topics[0].Hex() != p.eventHash { + return storage.TradeLog{}, ErrInvalidRFQTopic + } + o, err := p.ps.ParseRfqOrderFilled(log) + if err != nil { + return storage.TradeLog{}, err + } + res := storage.TradeLog{ + OrderHash: common.Hash(o.OrderHash).String(), + Maker: o.Maker.Hex(), + Taker: o.Taker.Hex(), + MakerToken: o.MakerToken.Hex(), + TakerToken: o.TakerToken.Hex(), + MakerTokenAmount: o.MakerTokenFilledAmount.String(), + TakerTokenAmount: o.TakerTokenFilledAmount.String(), + ContractAddress: o.Raw.Address.String(), + BlockNumber: o.Raw.BlockNumber, + TxHash: o.Raw.TxHash.String(), + LogIndex: uint64(o.Raw.Index), + Timestamp: blockTime * 1000, + } + return res, nil +} diff --git a/internal/parser/zxrfq/zxrfq.go b/internal/parser/zxrfq/zxrfq.go new file mode 100644 index 0000000..6861a76 --- /dev/null +++ b/internal/parser/zxrfq/zxrfq.go @@ -0,0 +1,322 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package zxrfq + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// RfqOrderFilledMetaData contains all meta data concerning the RfqOrderFilled contract. +var RfqOrderFilledMetaData = &bind.MetaData{ + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"orderHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"maker\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"taker\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"makerToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"takerToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"takerTokenFilledAmount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"makerTokenFilledAmount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"pool\",\"type\":\"bytes32\"}],\"name\":\"RfqOrderFilled\",\"type\":\"event\"}]", +} + +// RfqOrderFilledABI is the input ABI used to generate the binding from. +// Deprecated: Use RfqOrderFilledMetaData.ABI instead. +var RfqOrderFilledABI = RfqOrderFilledMetaData.ABI + +// RfqOrderFilled is an auto generated Go binding around an Ethereum contract. +type RfqOrderFilled struct { + RfqOrderFilledCaller // Read-only binding to the contract + RfqOrderFilledTransactor // Write-only binding to the contract + RfqOrderFilledFilterer // Log filterer for contract events +} + +// RfqOrderFilledCaller is an auto generated read-only Go binding around an Ethereum contract. +type RfqOrderFilledCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RfqOrderFilledTransactor is an auto generated write-only Go binding around an Ethereum contract. +type RfqOrderFilledTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RfqOrderFilledFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type RfqOrderFilledFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RfqOrderFilledSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type RfqOrderFilledSession struct { + Contract *RfqOrderFilled // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// RfqOrderFilledCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type RfqOrderFilledCallerSession struct { + Contract *RfqOrderFilledCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// RfqOrderFilledTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type RfqOrderFilledTransactorSession struct { + Contract *RfqOrderFilledTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// RfqOrderFilledRaw is an auto generated low-level Go binding around an Ethereum contract. +type RfqOrderFilledRaw struct { + Contract *RfqOrderFilled // Generic contract binding to access the raw methods on +} + +// RfqOrderFilledCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type RfqOrderFilledCallerRaw struct { + Contract *RfqOrderFilledCaller // Generic read-only contract binding to access the raw methods on +} + +// RfqOrderFilledTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type RfqOrderFilledTransactorRaw struct { + Contract *RfqOrderFilledTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewRfqOrderFilled creates a new instance of RfqOrderFilled, bound to a specific deployed contract. +func NewRfqOrderFilled(address common.Address, backend bind.ContractBackend) (*RfqOrderFilled, error) { + contract, err := bindRfqOrderFilled(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &RfqOrderFilled{RfqOrderFilledCaller: RfqOrderFilledCaller{contract: contract}, RfqOrderFilledTransactor: RfqOrderFilledTransactor{contract: contract}, RfqOrderFilledFilterer: RfqOrderFilledFilterer{contract: contract}}, nil +} + +// NewRfqOrderFilledCaller creates a new read-only instance of RfqOrderFilled, bound to a specific deployed contract. +func NewRfqOrderFilledCaller(address common.Address, caller bind.ContractCaller) (*RfqOrderFilledCaller, error) { + contract, err := bindRfqOrderFilled(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &RfqOrderFilledCaller{contract: contract}, nil +} + +// NewRfqOrderFilledTransactor creates a new write-only instance of RfqOrderFilled, bound to a specific deployed contract. +func NewRfqOrderFilledTransactor(address common.Address, transactor bind.ContractTransactor) (*RfqOrderFilledTransactor, error) { + contract, err := bindRfqOrderFilled(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &RfqOrderFilledTransactor{contract: contract}, nil +} + +// NewRfqOrderFilledFilterer creates a new log filterer instance of RfqOrderFilled, bound to a specific deployed contract. +func NewRfqOrderFilledFilterer(address common.Address, filterer bind.ContractFilterer) (*RfqOrderFilledFilterer, error) { + contract, err := bindRfqOrderFilled(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &RfqOrderFilledFilterer{contract: contract}, nil +} + +// bindRfqOrderFilled binds a generic wrapper to an already deployed contract. +func bindRfqOrderFilled(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := RfqOrderFilledMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_RfqOrderFilled *RfqOrderFilledRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _RfqOrderFilled.Contract.RfqOrderFilledCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_RfqOrderFilled *RfqOrderFilledRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _RfqOrderFilled.Contract.RfqOrderFilledTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_RfqOrderFilled *RfqOrderFilledRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _RfqOrderFilled.Contract.RfqOrderFilledTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_RfqOrderFilled *RfqOrderFilledCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _RfqOrderFilled.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_RfqOrderFilled *RfqOrderFilledTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _RfqOrderFilled.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_RfqOrderFilled *RfqOrderFilledTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _RfqOrderFilled.Contract.contract.Transact(opts, method, params...) +} + +// RfqOrderFilledRfqOrderFilledIterator is returned from FilterRfqOrderFilled and is used to iterate over the raw logs and unpacked data for RfqOrderFilled events raised by the RfqOrderFilled contract. +type RfqOrderFilledRfqOrderFilledIterator struct { + Event *RfqOrderFilledRfqOrderFilled // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *RfqOrderFilledRfqOrderFilledIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(RfqOrderFilledRfqOrderFilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(RfqOrderFilledRfqOrderFilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *RfqOrderFilledRfqOrderFilledIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *RfqOrderFilledRfqOrderFilledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// RfqOrderFilledRfqOrderFilled represents a RfqOrderFilled event raised by the RfqOrderFilled contract. +type RfqOrderFilledRfqOrderFilled struct { + OrderHash [32]byte + Maker common.Address + Taker common.Address + MakerToken common.Address + TakerToken common.Address + TakerTokenFilledAmount *big.Int + MakerTokenFilledAmount *big.Int + Pool [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRfqOrderFilled is a free log retrieval operation binding the contract event 0x829fa99d94dc4636925b38632e625736a614c154d55006b7ab6bea979c210c32. +// +// Solidity: event RfqOrderFilled(bytes32 orderHash, address maker, address taker, address makerToken, address takerToken, uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount, bytes32 pool) +func (_RfqOrderFilled *RfqOrderFilledFilterer) FilterRfqOrderFilled(opts *bind.FilterOpts) (*RfqOrderFilledRfqOrderFilledIterator, error) { + + logs, sub, err := _RfqOrderFilled.contract.FilterLogs(opts, "RfqOrderFilled") + if err != nil { + return nil, err + } + return &RfqOrderFilledRfqOrderFilledIterator{contract: _RfqOrderFilled.contract, event: "RfqOrderFilled", logs: logs, sub: sub}, nil +} + +// WatchRfqOrderFilled is a free log subscription operation binding the contract event 0x829fa99d94dc4636925b38632e625736a614c154d55006b7ab6bea979c210c32. +// +// Solidity: event RfqOrderFilled(bytes32 orderHash, address maker, address taker, address makerToken, address takerToken, uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount, bytes32 pool) +func (_RfqOrderFilled *RfqOrderFilledFilterer) WatchRfqOrderFilled(opts *bind.WatchOpts, sink chan<- *RfqOrderFilledRfqOrderFilled) (event.Subscription, error) { + + logs, sub, err := _RfqOrderFilled.contract.WatchLogs(opts, "RfqOrderFilled") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(RfqOrderFilledRfqOrderFilled) + if err := _RfqOrderFilled.contract.UnpackLog(event, "RfqOrderFilled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRfqOrderFilled is a log parse operation binding the contract event 0x829fa99d94dc4636925b38632e625736a614c154d55006b7ab6bea979c210c32. +// +// Solidity: event RfqOrderFilled(bytes32 orderHash, address maker, address taker, address makerToken, address takerToken, uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount, bytes32 pool) +func (_RfqOrderFilled *RfqOrderFilledFilterer) ParseRfqOrderFilled(log types.Log) (*RfqOrderFilledRfqOrderFilled, error) { + event := new(RfqOrderFilledRfqOrderFilled) + if err := _RfqOrderFilled.contract.UnpackLog(event, "RfqOrderFilled", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/internal/parser/zxrfq/zxrfq_test.go b/internal/parser/zxrfq/zxrfq_test.go new file mode 100644 index 0000000..3ff4c4a --- /dev/null +++ b/internal/parser/zxrfq/zxrfq_test.go @@ -0,0 +1,57 @@ +package zxrfq + +import ( + "context" + "encoding/json" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +func TestFetchEvent(t *testing.T) { + t.Skip() + p := MustNewParser() + require.Equal(t, p.abi.Events[RfqOrderFilledEvent].ID, common.HexToHash("0x829fa99d94dc4636925b38632e625736a614c154d55006b7ab6bea979c210c32")) + client, err := ethclient.Dial("https://mainnet.infura.io/v3/") + require.NoError(t, err) + logs, err := client.FilterLogs(context.Background(), ethereum.FilterQuery{ + BlockHash: nil, + FromBlock: big.NewInt(16376824), + ToBlock: big.NewInt(16376824), + Addresses: nil, + Topics: [][]common.Hash{ + { + common.HexToHash("0x829fa99d94dc4636925b38632e625736a614c154d55006b7ab6bea979c210c32"), + }, + }, + }) + require.NoError(t, err) + d, _ := json.Marshal(logs) + t.Log(string(d)) +} + +func TestParseEvent(t *testing.T) { + eventRaw := `{ + "address":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "topics":["0x829fa99d94dc4636925b38632e625736a614c154d55006b7ab6bea979c210c32"], + "data":"0x0bcf3d2e6e7c55d641843ce75fadb99eca10ed21d56d34c6bdfcf2492e2f12e300000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf900000000000000000000000074de5d4fcbf63e00296fd95d33236b9794016631000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000039c8e65eb874cc00000000000000000000000000000000000000000000000000000000014903f8810000000000000000000000000000000000000000000000000000000000000000", + "blockNumber":"0xf9e3f8", + "transactionHash":"0x09a4736a17994a57b91356c2dfce87af8b46e78279a0ef2d70eeacd0729e11a7", + "transactionIndex":"0x4", + "blockHash":"0x06b9e6a0fc43e123cbf624aef470b77afe260e67613a5ff0323d82a39acdc88e", + "logIndex":"0x16", + "removed":false}` + event := types.Log{} + err := json.Unmarshal([]byte(eventRaw), &event) + require.NoError(t, err) + p := MustNewParser() + log, err := p.Parse(event, uint64(time.Now().Unix())) + require.NoError(t, err) + t.Log(log) +} diff --git a/internal/server/server.go b/internal/server/server.go index 61fcc74..86f433f 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "time" "github.com/KyberNetwork/tradelogs/internal/storage" "github.com/gin-contrib/pprof" @@ -11,8 +12,8 @@ import ( "go.uber.org/zap" ) -const ( - maxTimeRange uint64 = 86400000 // 1 day +var ( + maxTimeRange uint64 = uint64(24 * time.Hour.Milliseconds()) ) // Server to serve the service.