Skip to content

Commit

Permalink
Merge pull request #87 from KyberNetwork/fix/parse-zeroxv3-trade
Browse files Browse the repository at this point in the history
fix: parse zeroxv3 trade
  • Loading branch information
vaigay authored Oct 23, 2024
2 parents 77214f1 + 9735fdf commit d01f891
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 11 deletions.
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ github.com/KyberNetwork/go-binance/v2 v2.0.3 h1:VsBBHXAJTxryBsdO6U9MyK+oHEJ9qZtQ
github.com/KyberNetwork/go-binance/v2 v2.0.3/go.mod h1:ihNzjOXDgxZ4cSENV2sQfXnO30wobXQPpqTuKxxUVjw=
github.com/KyberNetwork/kyber-trace-go v0.1.1 h1:YgzZb7jSg0Rgj7zFv9zUnSlB13Iksv1eVO5MSWAvjmM=
github.com/KyberNetwork/kyber-trace-go v0.1.1/go.mod h1:X6hVacmKMeOEOlFh4TyfEHaEVRerFQ5YLuQ4keRV3hw=
github.com/KyberNetwork/tradinglib v0.4.36 h1:h8+pKMPiqnR/3Q+wGq1JWYxP3qFJ3qXqsPJQETuJcFw=
github.com/KyberNetwork/tradinglib v0.4.36/go.mod h1:3Ciie58Qmd0mFSnUWAg6phbalao6f2GGcAZ6nxw/lcQ=
github.com/KyberNetwork/tradinglib v0.5.10 h1:4R9XWkfCDeisuUyxq8px1JMXNGl73+c3tIuqBX9jWl8=
github.com/KyberNetwork/tradinglib v0.5.10/go.mod h1:3Ciie58Qmd0mFSnUWAg6phbalao6f2GGcAZ6nxw/lcQ=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
Expand Down
162 changes: 162 additions & 0 deletions pkg/parser/zxrfqv3/docs.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pkg/parser/zxrfqv3/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var (
ErrInvalidRfqTrade = errors.New("invalid zeroxv3 rfq")
ErrNotFoundLogWithEmptyTopic = errors.New("not found log with empty topic")
ErrDetectRfqButCanNotParse = errors.New("detect zeroxv3 rfq trade but can not parse")
ErrorInputDataIsNotEnough = errors.New("input data is not enough")

//abi
ErrContractAddressIsZeroAddress = errors.New("contract address is zero address")
Expand Down
66 changes: 66 additions & 0 deletions pkg/parser/zxrfqv3/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package zxrfqv3

import (
"context"
"encoding/hex"
"fmt"
"log"
"math/big"
"strings"
"time"

"github.com/KyberNetwork/tradelogs/pkg/decoder"
Expand Down Expand Up @@ -32,6 +34,7 @@ type Parser struct {
}

const altEventHash = "0x0000000000000000000000000000000000000000000000000000000000000000"
const paddingByteSize = 32

func MustNewParserWithDeployer(cache *tracecall.Cache, ethClient *ethclient.Client, deployerAddress common.Address, contractAbiSupported ...ContractABI) *Parser {
if isZeroAddress(deployerAddress) {
Expand Down Expand Up @@ -312,6 +315,13 @@ func (p *Parser) getExecuteActionData(contractAddress common.Address, callFrame
}
contractCall, err := decoder.Decode(abi, callFrame.Input)
if err != nil {
actions, ok, errAction := p.DecodeExecuteInput(callFrame.Input)
if errAction != nil {
return nil, errAction
}
if ok {
return actions, nil
}
return nil, err
}
if contractCall.Name == executeFunctionName || contractCall.Name == executeMetaTxnFunctionName {
Expand Down Expand Up @@ -363,3 +373,59 @@ func (p *Parser) LogFromExchange(log ethereumTypes.Log) bool {
func (p *Parser) Address() string {
return ""
}

func (p *Parser) DecodeExecuteInput(input string) ([][]byte, bool, error) {
if len(input) < 10 {
return nil, false, ErrorInputDataIsNotEnough
}
if !strings.HasPrefix(input, executeFunctionSelector) && !strings.HasPrefix(input, executeMetaTxnFunctionSelector) {
return nil, false, nil
}

data, err := hex.DecodeString(input[10:])
if err != nil {
return nil, false, err
}
// function executeMetaTxn((address,address,uint256) slippage, bytes[] actions, bytes32 zid, address msgSender, bytes sig)
// function execute((address,address,uint256) slippage, bytes[] actions, bytes32 zid)
// skip: slippage (address,address,uint256): 32 + 32 + 32 = 96 bytes
offset := paddingByteSize * 3
if len(data) < offset {
return nil, false, ErrorInputDataIsNotEnough
}

offsetActions := new(big.Int).SetBytes(data[offset : offset+paddingByteSize]).Uint64()
// skip actions and zid
offset += paddingByteSize * 2

// with executeMetaTxn we need to skip msgSender and sig too
if strings.HasPrefix(input, executeMetaTxnFunctionSelector) {
offset += paddingByteSize * 2
}
if len(data) < offset {
return nil, false, ErrorInputDataIsNotEnough
}
actionsSize := new(big.Int).SetBytes(data[offsetActions : offsetActions+paddingByteSize]).Uint64()
baseOffset := int(offsetActions + paddingByteSize)
// skip actions size
offset += paddingByteSize
eachActionOffset := make([]int, actionsSize)

for i := 0; i < int(actionsSize); i++ {
actionOffset := new(big.Int).SetBytes(data[offset : offset+paddingByteSize]).Uint64()
eachActionOffset[i] = int(actionOffset)
offset += paddingByteSize
}

actions := make([][]byte, actionsSize)
for i := 0; i < int(actionsSize); i++ {
startOffset := baseOffset + eachActionOffset[i] + paddingByteSize
actionSize := int(new(big.Int).SetBytes(data[eachActionOffset[i]:startOffset]).Int64())
endIndex := startOffset + actionSize
if actionSize > len(data) {
endIndex = len(data)
}
actions[i] = data[startOffset:endIndex]
}
return actions, true, nil
}
102 changes: 93 additions & 9 deletions pkg/parser/zxrfqv3/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package zxrfqv3

import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"testing"

Expand All @@ -19,16 +21,16 @@ import (

const rpcURL = ""

func newParserTest(t *testing.T, contractABI ContractABI, needRpc bool) *Parser {
func newParserTest(t *testing.T, needRpc bool, contractABI ...ContractABI) *Parser {
var cache *tracecall.Cache
if needRpc {
ethClient, err := ethclient.Dial(rpcURL)
if err != nil {
panic(err)
t.Fatalf("failed to dial to rpc url: %s, err: %s", rpcURL, err)
}
cache = tracecall.NewCache(rpcnode.NewClient(zap.S(), ethClient))
}
return MustNewParser(cache, contractABI)
return MustNewParser(cache, contractABI...)
}

func getTestCaseData(t *testing.T, path string, result interface{}) {
Expand All @@ -46,10 +48,10 @@ func TestGetActionDataFromCallFame(t *testing.T) {
getTestCaseData(t, "./test/expected_input_rfq.json", &expectedInput)

contractAddress := common.HexToAddress("0x7966aF62034313D87Ede39380bf60f1A84c62BE7")
parser := newParserTest(t, ContractABI{
parser := newParserTest(t, false, ContractABI{
Address: contractAddress,
ContractType: DevContract,
}, false)
})
data, err := parser.getExecuteActionData(contractAddress, callFrame)
assert.NoError(t, err, "failed to get execute action data")
actionType, rawData, err := decodeCall(data[1])
Expand Down Expand Up @@ -78,10 +80,10 @@ func TestGetTransactionAndParseZeroxV3Rfq(t *testing.T) {

txHash := common.HexToHash("0xb244877d0cf0badcf2ac82dbb3cbf338b420ab5d1c6e6630fce4a4874121e427")
contractAddress := common.HexToAddress("0x7966aF62034313D87Ede39380bf60f1A84c62BE7")
parser := newParserTest(t, ContractABI{
parser := newParserTest(t, true, ContractABI{
Address: contractAddress,
ContractType: DevContract,
}, true)
})
ethClient, err := ethclient.Dial(rpcURL)
if err != nil {
t.Fatalf("failed to dial to rpc url: %s, err: %s", rpcURL, err)
Expand All @@ -96,8 +98,6 @@ func TestGetTransactionAndParseZeroxV3Rfq(t *testing.T) {
tradeLog, err := parser.Parse(*log, 1)
assert.NoError(t, err, "failed to parse trade log")
assert.Equal(t, expectedTradelog, tradeLog, "trade log not match")
//x, _ := json.MarshalIndent(tradeLog, "", "\t")
//fmt.Println(string(x))
}
}
assert.Greater(t, rfqCount, 0, "no rfq trade found")
Expand All @@ -111,3 +111,87 @@ func TestMustNewParserWithDeployer(t *testing.T) {
parser := MustNewParserWithDeployer(nil, ethClient, contractAddress)
assert.NotNil(t, parser, "failed to create parser")
}

func TestParseTradeLogsZeroxv3(t *testing.T) {
t.Skip("Need to add the rpc url that enables the trace call JSON-RPC")
type testCase struct {
TxHash common.Hash
ExpectedRfqTrade int
}

testCases := []testCase{
{
TxHash: common.HexToHash("0x8775544670f4c40271b3c9ab1b48e0648a1862bedd600c46f017e999b132e6a8"),
ExpectedRfqTrade: 1,
},
{
TxHash: common.HexToHash("0x99aba817ba6171488417da6990947413064eaff5200bc8d0be9f0c87580f19cc"),
ExpectedRfqTrade: 1,
},
{
TxHash: common.HexToHash("0xd90590fbf2799cbe63dac8a8a3dd05798b4ee0bd9077654fc1fc85cc09ad5bd1"),
ExpectedRfqTrade: 2,
},
{
TxHash: common.HexToHash("0x3fff39d6025924755ecfe4787e09cec3f87926791ce31ac79e58ba615463b564"),
ExpectedRfqTrade: 4,
},
{
TxHash: common.HexToHash("0x56ecb98917f20cfe1864a45e18d2afadbdf43b16b397464fda32bc08bb3018b6"),
ExpectedRfqTrade: 2,
},
{
TxHash: common.HexToHash("0xb81b2c0467c3c7b8f85a46c25b4b5fdb10256b36d0b2e1426c11f8ed1d710b61"),
ExpectedRfqTrade: 1,
},
}

swapContractAddress := common.HexToAddress("0x70bf6634eE8Cb27D04478f184b9b8BB13E5f4710")
gasLessContractAddress := common.HexToAddress("0x12D737470fB3ec6C3DeEC9b518100Bec9D520144")
parser := newParserTest(t, true, ContractABI{
Address: swapContractAddress,
ContractType: SwapContract,
}, ContractABI{
Address: gasLessContractAddress,
ContractType: SwapContract,
})
ethClient, err := ethclient.Dial(rpcURL)
if err != nil {
t.Fatalf("failed to dial to rpc url: %s, err: %s", rpcURL, err)
}

for _, tc := range testCases {
txReceipt, err := ethClient.TransactionReceipt(context.Background(), tc.TxHash)
require.NoError(t, err, "failed to get tx receipt")
rfqCount := 0
for _, log := range txReceipt.Logs {
if parser.contractABIs.containAddress(log.Address) && len(log.Topics) == 0 {
rfqCount++
tradeLog, err := parser.Parse(*log, 1)
assert.NoError(t, err, "failed to parse trade log")
t.Log(tradeLog)
}
}
assert.Equal(t, tc.ExpectedRfqTrade, rfqCount, fmt.Sprintf("Tx hash %s number rfq is not expected", tc.TxHash.Hex()))
}
}

func TestParserActionsWithCuttingBytes(t *testing.T) {
parser := MustNewParser(nil)
type inputTest struct {
InputData string `json:"input_data"`
Actions []string `json:"actions"`
}
var inputTestCases []inputTest
getTestCaseData(t, "./test/input_execute_data.json", &inputTestCases)
for _, input := range inputTestCases {
inputData := input.InputData
actions, ok, err := parser.DecodeExecuteInput(inputData)
assert.NoError(t, err, "failed to parse action data")
assert.True(t, ok, "failed to parse action data")
require.Equal(t, len(actions), len(input.Actions), "action length not match")
for i, action := range actions {
assert.Equal(t, input.Actions[i], hex.EncodeToString(action), "action not match")
}
}
}
3 changes: 3 additions & 0 deletions pkg/parser/zxrfqv3/settler_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const (
executeFunctionName = "execute"
executeMetaTxnFunctionName = "executeMetaTxn"
actionParamName = "actions"

executeFunctionSelector = "0x1fff991f"
executeMetaTxnFunctionSelector = "0xfd3ad6d4"
)

var mSettlerActionName map[FunctionName]FunctionABI
Expand Down
Loading

0 comments on commit d01f891

Please sign in to comment.