forked from evmos/ethermint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheip712_fuzzer_test.go
192 lines (155 loc) · 5.44 KB
/
eip712_fuzzer_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package eip712_test
import (
"fmt"
"math/rand"
"strings"
"github.com/evmos/ethermint/ethereum/eip712"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
type EIP712FuzzTestParams struct {
numTestObjects int
maxNumFieldsPerObject int
minStringLength int
maxStringLength int
randomFloatRange float64
maxArrayLength int
maxObjectDepth int
}
const (
numPrimitiveJSONTypes = 3
numJSONTypes = 5
asciiRangeStart = 65
asciiRangeEnd = 127
fuzzTestName = "Flatten"
)
const (
jsonBoolType = iota
jsonStringType = iota
jsonFloatType = iota
jsonArrayType = iota
jsonObjectType = iota
)
var params = EIP712FuzzTestParams{
numTestObjects: 16,
maxNumFieldsPerObject: 16,
minStringLength: 16,
maxStringLength: 48,
randomFloatRange: 120000000,
maxArrayLength: 8,
maxObjectDepth: 4,
}
// TestRandomPayloadFlattening generates many random payloads with different JSON values to ensure
// that Flattening works across all inputs.
// Note that this is a fuzz test, although it doesn't use Go's Fuzz testing suite, since there are
// variable input sizes, types, and fields. While it may be possible to translate a single input into
// a JSON object, it would require difficult parsing, and ultimately approximates our randomized unit
// tests as they are.
func (suite *EIP712TestSuite) TestRandomPayloadFlattening() {
// Re-seed rand generator
rand.Seed(rand.Int63())
for i := 0; i < params.numTestObjects; i++ {
suite.Run(fmt.Sprintf("%v%d", fuzzTestName, i), func() {
payload := suite.generateRandomPayload(i)
flattened, numMessages, err := eip712.FlattenPayloadMessages(payload)
suite.Require().NoError(err)
suite.Require().Equal(numMessages, i)
suite.verifyPayloadAgainstFlattened(payload, flattened)
})
}
}
// generateRandomPayload creates a random payload of the desired format, with random sub-objects.
func (suite *EIP712TestSuite) generateRandomPayload(numMessages int) gjson.Result {
payload := suite.createRandomJSONObject().Raw
msgs := make([]gjson.Result, numMessages)
for i := 0; i < numMessages; i++ {
msgs[i] = suite.createRandomJSONObject()
}
payload, err := sjson.Set(payload, msgsFieldName, msgs)
suite.Require().NoError(err)
return gjson.Parse(payload)
}
// createRandomJSONObject creates a JSON object with random fields.
func (suite *EIP712TestSuite) createRandomJSONObject() gjson.Result {
var err error
payloadRaw := ""
numFields := suite.createRandomIntInRange(0, params.maxNumFieldsPerObject)
for i := 0; i < numFields; i++ {
key := suite.createRandomString()
randField := suite.createRandomJSONField(i, 0)
payloadRaw, err = sjson.Set(payloadRaw, key, randField)
suite.Require().NoError(err)
}
return gjson.Parse(payloadRaw)
}
// createRandomJSONField creates a random field with a random JSON type, with the possibility of
// nested fields up to depth objects.
func (suite *EIP712TestSuite) createRandomJSONField(t int, depth int) interface{} {
switch t % numJSONTypes {
case jsonBoolType:
return suite.createRandomBoolean()
case jsonStringType:
return suite.createRandomString()
case jsonFloatType:
return suite.createRandomFloat()
case jsonArrayType:
return suite.createRandomJSONNestedArray(depth)
case jsonObjectType:
return suite.createRandomJSONNestedObject(depth)
default:
return nil
}
}
// createRandomJSONNestedArray creates an array of random nested JSON fields.
func (suite *EIP712TestSuite) createRandomJSONNestedArray(depth int) []interface{} {
arr := make([]interface{}, rand.Intn(params.maxArrayLength))
for i := range arr {
arr[i] = suite.createRandomJSONNestedField(depth)
}
return arr
}
// createRandomJSONNestedObject creates a key-value set of objects with random nested JSON fields.
func (suite *EIP712TestSuite) createRandomJSONNestedObject(depth int) interface{} {
numFields := rand.Intn(params.maxNumFieldsPerObject)
obj := make(map[string]interface{})
for i := 0; i < numFields; i++ {
subField := suite.createRandomJSONNestedField(depth)
obj[suite.createRandomString()] = subField
}
return obj
}
// createRandomJSONNestedField serves as a helper for createRandomJSONField and returns a random
// subfield to populate an array or object type.
func (suite *EIP712TestSuite) createRandomJSONNestedField(depth int) interface{} {
var newFieldType int
if depth == params.maxObjectDepth {
newFieldType = rand.Intn(numPrimitiveJSONTypes)
} else {
newFieldType = rand.Intn(numJSONTypes)
}
return suite.createRandomJSONField(newFieldType, depth+1)
}
func (suite *EIP712TestSuite) createRandomBoolean() bool {
return rand.Intn(2) == 0
}
func (suite *EIP712TestSuite) createRandomFloat() float64 {
return (rand.Float64() - 0.5) * params.randomFloatRange
}
func (suite *EIP712TestSuite) createRandomString() string {
bzLen := suite.createRandomIntInRange(params.minStringLength, params.maxStringLength)
bz := make([]byte, bzLen)
for i := 0; i < bzLen; i++ {
bz[i] = byte(suite.createRandomIntInRange(asciiRangeStart, asciiRangeEnd))
}
str := string(bz)
// Remove control characters, since they will make JSON invalid
str = strings.ReplaceAll(str, "{", "")
str = strings.ReplaceAll(str, "}", "")
str = strings.ReplaceAll(str, "]", "")
str = strings.ReplaceAll(str, "[", "")
return str
}
// createRandomIntInRange provides a random integer between [min, max)
func (suite *EIP712TestSuite) createRandomIntInRange(min int, max int) int {
return rand.Intn(max-min) + min
}