From 023f5dfc04eaa8b9f7597617646df4dc7936ca27 Mon Sep 17 00:00:00 2001 From: minhthanh Date: Wed, 14 Aug 2024 16:59:22 +0700 Subject: [PATCH] handle partial fill for bebop (#64) * handle partial fill for bebop --- pkg/parser/bebop/bebop_test.go | 74 +++++++-- pkg/parser/bebop/parser.go | 276 +++++++++++++++++++++++++-------- 2 files changed, 273 insertions(+), 77 deletions(-) diff --git a/pkg/parser/bebop/bebop_test.go b/pkg/parser/bebop/bebop_test.go index dfd2157..01e50bd 100644 --- a/pkg/parser/bebop/bebop_test.go +++ b/pkg/parser/bebop/bebop_test.go @@ -31,8 +31,8 @@ func TestFetchEvent(t *testing.T) { require.NoError(t, err) logs, err := client.FilterLogs(context.Background(), ethereum.FilterQuery{ BlockHash: nil, - FromBlock: big.NewInt(20025442), - ToBlock: big.NewInt(20025442), + FromBlock: big.NewInt(19932179), + ToBlock: big.NewInt(19932179), Addresses: nil, Topics: [][]common.Hash{ { @@ -46,10 +46,10 @@ func TestFetchEvent(t *testing.T) { t.Log(string(d)) } -func TestParseEvent(t *testing.T) { +func TestParseAggregateOrderEvent(t *testing.T) { t.Skip("Need to add the rpc url that enables the trace call JSON-RPC") - eventRaw := `[{"address":"0xbbbbbbb520d69a9775e85b458c58c648259fad5f","topics":["0xadd7095becdaa725f0f33243630938c861b0bba83dfd217d4055701aa768ec2e","0x00000000000000000000000000000000b05c492e829fd9c00000000000000000"],"data":"0x","blockNumber":"0x1319062","transactionHash":"0x0f7567ffd5fc92aa552c5e6ebf8173514693aadd5a888c816b4735a931e8c8c3","transactionIndex":"0xe","blockHash":"0x3015d9d1960cab845f333c49f8cb0d81fa89b2c837de8ac22cd645810bb18c83","logIndex":"0x5a","removed":false}]` - events := []types.Log{} + eventRaw := `{"address":"0xbbbbbbb520d69a9775e85b458c58c648259fad5f","topics":["0xadd7095becdaa725f0f33243630938c861b0bba83dfd217d4055701aa768ec2e","0x000000000000000000000000000000006f0760aefdfe33400000000000000000"],"data":"0x","blockNumber":"0x138de04","transactionHash":"0xb9d13c7057d6d0779a14023d8ce469a8266ea89eb2ca4e6f4c085ef0929c6f08","transactionIndex":"0x8","blockHash":"0x54a780bfa027b8cfb16a40bb86be066795e84d6a124582f9a85b8c662f27fda9","logIndex":"0x46","removed":false}` + events := types.Log{} err := json.Unmarshal([]byte(eventRaw), &events) require.NoError(t, err) ethClient, err := ethclient.Dial(rpcURL) @@ -58,15 +58,59 @@ func TestParseEvent(t *testing.T) { } traceCalls := tracecall.NewCache(rpcnode.NewClient(ethClient)) p := MustNewParser(traceCalls) - for _, event := range events { - log, err := p.Parse(event, uint64(time.Now().Unix())) - require.NoError(t, err) - require.Equal(t, log.EventHash, p.eventHash) - t.Log(log.Maker) - t.Log(log.MakerToken) - t.Log(log.MakerTokenAmount) - t.Log(log.Taker) - t.Log(log.TakerToken) - t.Log(log.TakerTokenAmount) + log, err := p.Parse(events, uint64(time.Now().Unix())) + require.NoError(t, err) + require.Equal(t, log.EventHash, p.eventHash) + t.Log(log.Maker) + t.Log(log.MakerToken) + t.Log(log.MakerTokenAmount) + t.Log(log.Taker) + t.Log(log.TakerToken) + t.Log(log.TakerTokenAmount) +} + +func TestParseMultiOrderEvent(t *testing.T) { + t.Skip("Need to add the rpc url that enables the trace call JSON-RPC") + eventRaw := `{"address":"0xbbbbbbb520d69a9775e85b458c58c648259fad5f","topics":["0xadd7095becdaa725f0f33243630938c861b0bba83dfd217d4055701aa768ec2e","0x0000000000000000000000000000000027611cb25303fa700000000000000001"],"data":"0x","blockNumber":"0x133842c","transactionHash":"0xff46ac555ec7da7aa484864dc0df90217b7f46dfa51627c20ef6e25451c64b15","transactionIndex":"0x10","blockHash":"0xb03cd01d48456c60cdf445e35b0a4d7672b80d801245e516336596f0d5b77951","logIndex":"0x85","removed":false}` + events := types.Log{} + err := json.Unmarshal([]byte(eventRaw), &events) + require.NoError(t, err) + ethClient, err := ethclient.Dial(rpcURL) + if err != nil { + panic(err) + } + traceCalls := tracecall.NewCache(rpcnode.NewClient(ethClient)) + p := MustNewParser(traceCalls) + log, err := p.Parse(events, uint64(time.Now().Unix())) + require.NoError(t, err) + require.Equal(t, log.EventHash, p.eventHash) + t.Log(log.Maker) + t.Log(log.MakerToken) + t.Log(log.MakerTokenAmount) + t.Log(log.Taker) + t.Log(log.TakerToken) + t.Log(log.TakerTokenAmount) +} + +func TestParseSingleOrderEvent(t *testing.T) { + t.Skip("Need to add the rpc url that enables the trace call JSON-RPC") + eventRaw := `{"address":"0xbbbbbbb520d69a9775e85b458c58c648259fad5f","topics":["0xadd7095becdaa725f0f33243630938c861b0bba83dfd217d4055701aa768ec2e","0x0000000000000000000000000000000084e2ab041ba07e400000000000000000"],"data":"0x","blockNumber":"0x1302413","transactionHash":"0xa227c4aed6f3ae86cd8ba02349d0ae5be78337266416ac65cf8a8d095a2f572e","transactionIndex":"0x47","blockHash":"0xbb4a5ae13e2a66f83f444f24078ac3fb6d0b09f610c48d920d3b89d4f20ea86c","logIndex":"0xd8","removed":false}` + events := types.Log{} + err := json.Unmarshal([]byte(eventRaw), &events) + require.NoError(t, err) + ethClient, err := ethclient.Dial(rpcURL) + if err != nil { + panic(err) } + traceCalls := tracecall.NewCache(rpcnode.NewClient(ethClient)) + p := MustNewParser(traceCalls) + log, err := p.Parse(events, uint64(time.Now().Unix())) + require.NoError(t, err) + require.Equal(t, log.EventHash, p.eventHash) + t.Log(log.Maker) + t.Log(log.MakerToken) + t.Log(log.MakerTokenAmount) + t.Log(log.Taker) + t.Log(log.TakerToken) + t.Log(log.TakerTokenAmount) } diff --git a/pkg/parser/bebop/parser.go b/pkg/parser/bebop/parser.go index d3f0b28..7b288b4 100644 --- a/pkg/parser/bebop/parser.go +++ b/pkg/parser/bebop/parser.go @@ -25,6 +25,10 @@ const ( OrderParam = "order" ) +var ( + ErrParamNotFound = errors.New("param not found") +) + type SingleOrder struct { Expiry *big.Int `json:"expiry"` TakerAddress string `json:"taker_address"` @@ -40,31 +44,49 @@ type SingleOrder struct { } type MultiOrder struct { - Expiry *big.Int `json:"expiry"` - TakerAddress string `json:"taker_address"` - MakerAddress string `json:"maker_address"` - MakerNonce *big.Int `json:"maker_nonce"` - TakerTokens json.RawMessage `json:"taker_tokens"` - MakerTokens json.RawMessage `json:"maker_tokens"` - TakerAmounts json.RawMessage `json:"taker_amounts"` - MakerAmounts json.RawMessage `json:"maker_amounts"` - Receiver string `json:"receiver"` - PackedCommands *big.Int `json:"packed_commands"` - Flags *big.Int `json:"flags"` + Expiry *big.Int `json:"expiry"` + TakerAddress string `json:"taker_address"` + MakerAddress string `json:"maker_address"` + MakerNonce *big.Int `json:"maker_nonce"` + TakerTokens []string `json:"taker_tokens"` + MakerTokens []string `json:"maker_tokens"` + TakerAmounts []*big.Int `json:"taker_amounts"` + MakerAmounts []*big.Int `json:"maker_amounts"` + Receiver string `json:"receiver"` + Commands []byte `json:"commands"` + Flags *big.Int `json:"flags"` } type AggregateOrder struct { - Expiry *big.Int `json:"expiry"` - TakerAddress string `json:"taker_address"` - MakerAddresses json.RawMessage `json:"maker_addresses"` - MakerNonces json.RawMessage `json:"maker_nonces"` - TakerTokens json.RawMessage `json:"taker_tokens"` - MakerTokens json.RawMessage `json:"maker_tokens"` - TakerAmounts json.RawMessage `json:"taker_amounts"` - MakerAmounts json.RawMessage `json:"maker_amounts"` - Receiver string `json:"receiver"` - PackedCommands *big.Int `json:"packed_commands"` - Flags *big.Int `json:"flags"` + Expiry *big.Int `json:"expiry"` + TakerAddress string `json:"taker_address"` + MakerAddresses []string `json:"maker_addresses"` + MakerNonces []*big.Int `json:"maker_nonces"` + TakerTokens [][]string `json:"taker_tokens"` + MakerTokens [][]string `json:"maker_tokens"` + TakerAmounts [][]*big.Int `json:"taker_amounts"` + MakerAmounts [][]*big.Int `json:"maker_amounts"` + Receiver string `json:"receiver"` + Commands []byte `json:"commands"` + Flags *big.Int `json:"flags"` +} + +type OldSingleQuote struct { + UseOldAmount bool `json:"useOldAmount"` + MakerAmount *big.Int `json:"makerAmount"` + MakerNonce *big.Int `json:"makerNonce"` +} + +type OldMultiQuote struct { + UseOldAmount bool `json:"useOldAmount"` + MakerAmounts []*big.Int `json:"makerAmounts"` + MakerNonce *big.Int `json:"makerNonce"` +} + +type OldAggregateQuote struct { + UseOldAmount bool `json:"useOldAmount"` + MakerAmounts [][]*big.Int `json:"makerAmounts"` + MakerNonces []*big.Int `json:"makerNonces"` } type Parser struct { @@ -173,55 +195,33 @@ func (p *Parser) ParseFromInternalCall(order storage.TradeLog, internalCall type if err != nil { return order, err } - + var filledTakerAmount *big.Int + for _, param := range contractCall.Params { + if param.Name != "filledTakerAmount" { + continue + } + v, ok := param.Value.(*big.Int) + if ok { + filledTakerAmount = v + } + break + } + if filledTakerAmount == nil { + return order, ErrParamNotFound + } for _, param := range contractCall.Params { if param.Name != OrderParam { continue } switch { case p.singleOrderFunc.Has(contractCall.Name): - var rfqOrder SingleOrder - if err := unpackOrder(param.Value, &rfqOrder); err != nil { - return order, err - } - order.MakerToken = rfqOrder.MakerToken - order.TakerToken = rfqOrder.TakerToken - order.Maker = rfqOrder.MakerAddress - order.Taker = rfqOrder.TakerAddress - order.MakerTokenAmount = rfqOrder.MakerAmount.String() - order.TakerTokenAmount = rfqOrder.TakerAmount.String() - if rfqOrder.Expiry != nil { - order.Expiry = rfqOrder.Expiry.Uint64() - } + return p.parseSingleSwap(order, contractCall, param, filledTakerAmount) case p.multiOrderFunc.Has(contractCall.Name): - var rfqOrder MultiOrder - if err := unpackOrder(param.Value, &rfqOrder); err != nil { - return order, err - } - order.MakerToken = string(rfqOrder.MakerTokens) - order.TakerToken = string(rfqOrder.TakerTokens) - order.Maker = rfqOrder.MakerAddress - order.Taker = rfqOrder.TakerAddress - order.MakerTokenAmount = string(rfqOrder.MakerAmounts) - order.TakerTokenAmount = string(rfqOrder.TakerAmounts) - if rfqOrder.Expiry != nil { - order.Expiry = rfqOrder.Expiry.Uint64() - } + return p.parseMultiSwap(order, contractCall, param, filledTakerAmount) case p.aggregateOrderFunc.Has(contractCall.Name): - var rfqOrder AggregateOrder - if err := unpackOrder(param.Value, &rfqOrder); err != nil { - return order, err - } - order.MakerToken = string(rfqOrder.MakerTokens) - order.TakerToken = string(rfqOrder.TakerTokens) - order.Maker = string(rfqOrder.MakerAddresses) - order.Taker = rfqOrder.TakerAddress - order.MakerTokenAmount = string(rfqOrder.MakerAmounts) - order.TakerTokenAmount = string(rfqOrder.TakerAmounts) - if rfqOrder.Expiry != nil { - order.Expiry = rfqOrder.Expiry.Uint64() - } + return p.parseAggregateSwap(order, contractCall, param, filledTakerAmount) } + break } return order, nil @@ -283,3 +283,155 @@ func (p *Parser) LogFromExchange(log ethereumTypes.Log) bool { len(log.Topics) > 0 && strings.EqualFold(log.Topics[0].String(), p.eventHash) } + +func (p *Parser) parseSingleSwap(order storage.TradeLog, + contractCall *tradingTypes.ContractCall, + orderParam tradingTypes.ContractCallParam, + fillTakerAmount *big.Int) (storage.TradeLog, error) { + var rfqOrder SingleOrder + if err := unpackOrder(orderParam.Value, &rfqOrder); err != nil { + return order, err + } + + for _, param := range contractCall.Params { + if param.Name != "takerQuoteInfo" { + continue + } + var oldOrder OldSingleQuote + if err := unpackOrder(param.Value, &oldOrder); err == nil && oldOrder.UseOldAmount { + rfqOrder.MakerAmount = oldOrder.MakerAmount + } + break + } + + if fillTakerAmount.Cmp(big.NewInt(0)) > 0 && fillTakerAmount.Cmp(rfqOrder.TakerAmount) < 0 { + tmp := big.NewInt(0).Mul(rfqOrder.MakerAmount, fillTakerAmount) + rfqOrder.MakerAmount = tmp.Div(tmp, rfqOrder.TakerAmount) + rfqOrder.TakerAmount = fillTakerAmount + } + + order.MakerToken = rfqOrder.MakerToken + order.TakerToken = rfqOrder.TakerToken + order.Maker = rfqOrder.MakerAddress + order.Taker = rfqOrder.TakerAddress + order.MakerTokenAmount = rfqOrder.MakerAmount.String() + order.TakerTokenAmount = rfqOrder.TakerAmount.String() + if rfqOrder.Expiry != nil { + order.Expiry = rfqOrder.Expiry.Uint64() + } + + return order, nil +} + +func (p *Parser) parseMultiSwap(order storage.TradeLog, + contractCall *tradingTypes.ContractCall, + orderParam tradingTypes.ContractCallParam, + fillTakerAmount *big.Int) (storage.TradeLog, error) { + var rfqOrder MultiOrder + if err := unpackOrder(orderParam.Value, &rfqOrder); err != nil { + return order, err + } + + for _, param := range contractCall.Params { + if param.Name != "takerQuoteInfo" { + continue + } + var oldOrder OldMultiQuote + if err := unpackOrder(param.Value, &oldOrder); err == nil && oldOrder.UseOldAmount { + rfqOrder.MakerAmounts = oldOrder.MakerAmounts + } + break + } + + if len(rfqOrder.TakerTokens) == 1 { // many to one don't support partial, just handle one - many + if fillTakerAmount.Cmp(big.NewInt(0)) > 0 && fillTakerAmount.Cmp(rfqOrder.TakerAmounts[0]) < 0 { + for j := range rfqOrder.MakerAmounts { + tmp := big.NewInt(0).Mul(rfqOrder.MakerAmounts[j], fillTakerAmount) + rfqOrder.MakerAmounts[j] = tmp.Div(tmp, rfqOrder.TakerAmounts[0]) + rfqOrder.TakerAmounts[0] = fillTakerAmount + } + } + } + + makerTokens, _ := json.Marshal(rfqOrder.MakerTokens) + order.MakerToken = string(makerTokens) + takerTokens, _ := json.Marshal(rfqOrder.TakerTokens) + order.TakerToken = string(takerTokens) + order.Maker = rfqOrder.MakerAddress + order.Taker = rfqOrder.TakerAddress + makerAmounts, _ := json.Marshal(rfqOrder.MakerAmounts) + order.MakerTokenAmount = string(makerAmounts) + takerAmounts, _ := json.Marshal(rfqOrder.TakerAmounts) + order.TakerTokenAmount = string(takerAmounts) + if rfqOrder.Expiry != nil { + order.Expiry = rfqOrder.Expiry.Uint64() + } + + return order, nil +} + +func (p *Parser) parseAggregateSwap(order storage.TradeLog, + contractCall *tradingTypes.ContractCall, + orderParam tradingTypes.ContractCallParam, + filledTakerAmount *big.Int) (storage.TradeLog, error) { + + var rfqOrder AggregateOrder + if err := unpackOrder(orderParam.Value, &rfqOrder); err != nil { + return order, err + } + + for _, param := range contractCall.Params { + if param.Name != "takerQuoteInfo" { + continue + } + var oldOrder OldAggregateQuote + if err := unpackOrder(param.Value, &oldOrder); err == nil && oldOrder.UseOldAmount { + rfqOrder.MakerAmounts = oldOrder.MakerAmounts + } + break + } + quoteTakerAmount := getAggregateOrderInfo(rfqOrder) + if filledTakerAmount.Cmp(big.NewInt(0)) > 0 && filledTakerAmount.Cmp(quoteTakerAmount) < 0 { + for i := range rfqOrder.MakerAmounts { + for j := range rfqOrder.MakerAmounts[i] { + tmp := big.NewInt(0).Mul(rfqOrder.MakerAmounts[i][j], filledTakerAmount) + rfqOrder.MakerAmounts[i][j] = tmp.Div(tmp, quoteTakerAmount) + + tmp = big.NewInt(0).Mul(rfqOrder.TakerAmounts[i][j], filledTakerAmount) + rfqOrder.TakerAmounts[i][j] = tmp.Div(tmp, quoteTakerAmount) + } + } + } + + makerTokens, _ := json.Marshal(rfqOrder.MakerTokens) + order.MakerToken = string(makerTokens) + takerTokens, _ := json.Marshal(rfqOrder.TakerTokens) + order.TakerToken = string(takerTokens) + makerAddress, _ := json.Marshal(rfqOrder.MakerAddresses) + order.Maker = string(makerAddress) + order.Taker = rfqOrder.TakerAddress + makerAmounts, _ := json.Marshal(rfqOrder.MakerAmounts) + order.MakerTokenAmount = string(makerAmounts) + takerAmounts, _ := json.Marshal(rfqOrder.TakerAmounts) + order.TakerTokenAmount = string(takerAmounts) + if rfqOrder.Expiry != nil { + order.Expiry = rfqOrder.Expiry.Uint64() + } + + return order, nil +} + +func getAggregateOrderInfo(order AggregateOrder) *big.Int { + commandsInd := 0 + quoteTakerAmount := big.NewInt(0) + for i := range order.TakerTokens { + commandsInd += len(order.MakerTokens[i]) + for j := range order.TakerTokens[i] { + curCommand := order.Commands[commandsInd+j] + if curCommand != 0x08 { + quoteTakerAmount = quoteTakerAmount.Add(quoteTakerAmount, order.TakerAmounts[i][j]) + } + } + } + return quoteTakerAmount +}