Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added support for job returning results in hex array #1174

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,9 @@ var SwitchClientDuration = 5 * EpochLength

// HexReturnType is the ReturnType for a job if that job returns a hex value
var HexReturnType = "hex"

// HexArrayReturnType is the ReturnType for a job if that job returns a hex array value
var HexArrayReturnType = "^hexArray\\[\\d+\\]$"

// HexArrayExtractIndexRegex will be used as a regular expression to extract index from hexArray return type
var HexArrayExtractIndexRegex = `^hexArray\[(\d+)\]$`
17 changes: 15 additions & 2 deletions utils/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,11 +658,16 @@ func TestGetDataToCommitFromJob(t *testing.T) {
Url: "https://api.gemini.com/v1/pubticker/ethusd/apiKey=${SAMPLE_API_KEY_NEW}",
}

postJob := bindings.StructsJob{Id: 1, SelectorType: 0, Weight: 100,
postJobUniswapV3 := bindings.StructsJob{Id: 1, SelectorType: 0, Weight: 100,
Power: 2, Name: "ethusd_sample", Selector: "result",
Url: `{"type": "POST","url": "https://rpc.ankr.com/eth","body": {"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xb27308f9f90d607463bb33ea1bebb41c27ce5ab6","data":"0xf7729d43000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":5},"header": {"content-type": "application/json"}, "returnType": "hex"}`,
}

postJobUniswapV2 := bindings.StructsJob{Id: 1, SelectorType: 0, Weight: 100,
Power: 6, Name: "ethusd_sample", Selector: "result",
Url: `{"type": "POST","url": "https://rpc.ankr.com/eth","body": {"jsonrpc":"2.0","id":7269270904970082,"method":"eth_call","params":[{"from":"0x0000000000000000000000000000000000000000","data":"0xd06ca61f0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000050de6856358cc35f3a9a57eaaa34bd4cb707d2cd0000000000000000000000008e870d67f660d95d5be530380d0ec0bd388289e1","to":"0x7a250d5630b4cf539739df2c5dacb4c659f2488d"},"latest"]},"header": {"content-type": "application/json"}, "returnType": "hexArray[1]"}`,
}

invalidDataSourceStructJob := bindings.StructsJob{Id: 1, SelectorType: 0, Weight: 100,
Power: 2, Name: "ethusd_sample", Selector: "result",
Url: `{"type": true,"url1": {}}`,
Expand Down Expand Up @@ -700,7 +705,7 @@ func TestGetDataToCommitFromJob(t *testing.T) {
{
name: "Test 3: When GetDataToCommitFromJob() executes successfully for a POST Job",
args: args{
job: postJob,
job: postJobUniswapV3,
},
wantErr: false,
},
Expand All @@ -720,6 +725,14 @@ func TestGetDataToCommitFromJob(t *testing.T) {
want: nil,
wantErr: true,
},
{
name: "Test 6: When GetDataToCommitFromJob() executes successfully for a POST uniswap v2 Job",
args: args{
job: postJobUniswapV2,
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
102 changes: 102 additions & 0 deletions utils/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"
mathRand "math/rand"
"razor/core"
"regexp"
"sort"
"strconv"
"strings"
Expand All @@ -26,6 +27,9 @@ func ConvertToNumber(num interface{}, returnType string) (*big.Float, error) {
if strings.ToLower(returnType) == core.HexReturnType {
return ConvertHexToBigFloat(v)
}
if isHexArrayPattern(returnType) {
return HandleHexArray(v, returnType)
}
convertedNumber, err := strconv.ParseFloat(v, 64)
if err != nil {
log.Error("Error in converting from string to float: ", err)
Expand Down Expand Up @@ -192,3 +196,101 @@ func Shuffle(slice []uint32) []uint32 {
}
return copiedSlice
}

func HandleHexArray(hexStr string, returnType string) (*big.Float, error) {
decodedHexArray, err := decodeHexString(hexStr)
if err != nil {
log.Error("Error in decoding hex array: ", err)
return big.NewFloat(0), err
}
log.Info("HandleHexArray: decoded hex array: ", decodedHexArray)

index, err := extractIndex(returnType)
if err != nil {
log.Error("Error in extracting value from decoded hex array: ", err)
return big.NewFloat(0), err
}
log.Debug("HandleHexArray: extracted index: ", index)

// Check if index is within the bounds of decodedHexArray
if index < 0 || index >= len(decodedHexArray) {
log.Error("extracted index is out of bounds for decoded hex array")
return big.NewFloat(0), errors.New("extracted index is out of bounds")
}

// decodedHexArray[index] returns value in wei, so it needs to be converted to eth
valueInEth, err := ConvertWeiToEth(decodedHexArray[index])
if err != nil {
log.Error("Error in converting wei to eth: ", err)
return big.NewFloat(0), err
}

return valueInEth, nil
}

func decodeHexString(hexStr string) ([]*big.Int, error) {
// Remove the "0x" prefix if present
hexStr = strings.TrimPrefix(hexStr, "0x")
// The length of uint256 in hex (32 bytes)
const uint256HexLength = 64

// Make sure the string length is at least enough for the offset and length
if len(hexStr) < 2*uint256HexLength {
return nil, errors.New("hex string too short to contain valid data")
}

// Getting the starting position of the array data (skipping this step as per Ethereum ABI encoding)
// Skip the length of the array (next 32 bytes)
lengthStr := hexStr[uint256HexLength : 2*uint256HexLength]
length, success := new(big.Int).SetString(lengthStr, 16)
if !success {
log.Error("Invalid length of the array from the hex string")
return nil, errors.New("invalid length")
}

// The remaining part of the string are the uint256 values
valuesStr := hexStr[2*uint256HexLength:]

// Each value is 32 bytes long, so check if the length matches
if len(valuesStr) != int(length.Int64())*uint256HexLength {
return nil, errors.New("data length does not match length specifier")
}

var values []*big.Int
for i := 0; i < int(length.Int64()); i++ {
start := i * uint256HexLength
end := start + uint256HexLength
n := new(big.Int)
n, success := n.SetString(valuesStr[start:end], 16)
if !success {
log.Errorf("Invalid uint256 value at index %d", i)
return nil, errors.New("invalid uint256 value at index")
}
values = append(values, n)
}

return values, nil
}

func extractIndex(s string) (int, error) {
re := regexp.MustCompile(core.HexArrayExtractIndexRegex)

matches := re.FindStringSubmatch(s)
if len(matches) < 2 {
return 0, errors.New("no index found in string")
}

// Converting the captured substring to an integer
index, err := strconv.Atoi(matches[1])
if err != nil {
return 0, errors.New("invalid index format in string")
}

return index, nil
}

func isHexArrayPattern(s string) bool {
pattern := core.HexArrayReturnType
re := regexp.MustCompile(pattern)
return re.MatchString(s)
}
Loading
Loading