hermez-node/api/exits_test.go
api/exits_test.goRaw
1package api
2
3import (
4 "fmt"
5 "testing"
6
7 "github.com/hermeznetwork/hermez-node/api/apitypes"
8 "github.com/hermeznetwork/hermez-node/common"
9 "github.com/hermeznetwork/hermez-node/db/historydb"
10 "github.com/mitchellh/copystructure"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13)
14
15type testCVP struct {
16 Root string
17 Siblings []string
18 OldKey string
19 OldValue string
20 IsOld0 bool
21 Key string
22 Value string
23 Fnc int
24}
25
26type testExit struct {
27 ItemID uint64 `json:"itemId"`
28 BatchNum common.BatchNum `json:"batchNum"`
29 AccountIdx string `json:"accountIndex"`
30 BJJ apitypes.HezBJJ `json:"bjj"`
31 EthAddr apitypes.HezEthAddr `json:"hezEthereumAddress"`
32 MerkleProof testCVP `json:"merkleProof"`
33 Balance string `json:"balance"`
34 InstantWithdrawn *int64 `json:"instantWithdraw"`
35 DelayedWithdrawRequest *int64 `json:"delayedWithdrawRequest"`
36 DelayedWithdrawn *int64 `json:"delayedWithdraw"`
37 Token historydb.TokenWithUSD `json:"token"`
38}
39
40type testExitsResponse struct {
41 Exits []testExit `json:"exits"`
42 PendingItems uint64 `json:"pendingItems"`
43}
44
45func (t testExitsResponse) GetPending() (pendingItems, lastItemID uint64) {
46 if len(t.Exits) == 0 {
47 return 0, 0
48 }
49 pendingItems = t.PendingItems
50 lastItemID = t.Exits[len(t.Exits)-1].ItemID
51 return pendingItems, lastItemID
52}
53
54func (t testExitsResponse) New() Pendinger { return &testExitsResponse{} }
55
56func (t *testExitsResponse) Len() int {
57 return len(t.Exits)
58}
59
60func genTestExits(
61 commonExits []common.ExitInfo,
62 tokens []historydb.TokenWithUSD,
63 accs []common.Account,
64) []testExit {
65 allExits := []testExit{}
66 for _, exit := range commonExits {
67 token := getTokenByIdx(exit.AccountIdx, tokens, accs)
68 siblings := []string{}
69 for i := 0; i < len(exit.MerkleProof.Siblings); i++ {
70 siblings = append(siblings, exit.MerkleProof.Siblings[i].String())
71 }
72 acc := getAccountByIdx(exit.AccountIdx, accs)
73 allExits = append(allExits, testExit{
74 BatchNum: exit.BatchNum,
75 AccountIdx: idxToHez(exit.AccountIdx, token.Symbol),
76 BJJ: apitypes.NewHezBJJ(acc.BJJ),
77 EthAddr: apitypes.NewHezEthAddr(acc.EthAddr),
78 MerkleProof: testCVP{
79 Root: exit.MerkleProof.Root.String(),
80 Siblings: siblings,
81 OldKey: exit.MerkleProof.OldKey.String(),
82 OldValue: exit.MerkleProof.OldValue.String(),
83 IsOld0: exit.MerkleProof.IsOld0,
84 Key: exit.MerkleProof.Key.String(),
85 Value: exit.MerkleProof.Value.String(),
86 Fnc: exit.MerkleProof.Fnc,
87 },
88 Balance: exit.Balance.String(),
89 InstantWithdrawn: exit.InstantWithdrawn,
90 DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
91 DelayedWithdrawn: exit.DelayedWithdrawn,
92 Token: token,
93 })
94 }
95 return allExits
96}
97
98func TestGetExits(t *testing.T) {
99 endpoint := apiURL + "exits"
100 fetchedExits := []testExit{}
101 appendIter := func(intr interface{}) {
102 for i := 0; i < len(intr.(*testExitsResponse).Exits); i++ {
103 tmp, err := copystructure.Copy(intr.(*testExitsResponse).Exits[i])
104 if err != nil {
105 panic(err)
106 }
107 fetchedExits = append(fetchedExits, tmp.(testExit))
108 }
109 }
110 // Get all (no filters)
111 limit := 8
112 path := fmt.Sprintf("%s?limit=%d", endpoint, limit)
113 err := doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
114 assert.NoError(t, err)
115 assertExitAPIs(t, tc.exits, fetchedExits)
116
117 // Get by ethAddr
118 fetchedExits = []testExit{}
119 limit = 7
120 var account testAccount
121 for _, tx := range tc.txs {
122 found := false
123 if tx.Type == common.TxTypeExit {
124 for i := 0; i < len(tc.accounts); i++ {
125 if tx.FromIdx != nil && string(tc.accounts[i].Idx) == *tx.FromIdx {
126 account = tc.accounts[i]
127 break
128 }
129 }
130 }
131 if found {
132 break
133 }
134 }
135 path = fmt.Sprintf(
136 "%s?hezEthereumAddress=%s&limit=%d",
137 endpoint, account.EthAddr, limit,
138 )
139 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
140 assert.NoError(t, err)
141 var accountExits []testExit
142 for i := range tc.exits {
143 for _, acc := range tc.accounts {
144 if string(acc.Idx) == tc.exits[i].AccountIdx {
145 if acc.EthAddr == account.EthAddr {
146 accountExits = append(accountExits, tc.exits[i])
147 }
148 }
149 }
150 }
151 assertExitAPIs(t, accountExits, fetchedExits)
152 // Get by bjj
153 fetchedExits = []testExit{}
154 limit = 6
155 path = fmt.Sprintf(
156 "%s?BJJ=%s&limit=%d",
157 endpoint, account.PublicKey, limit,
158 )
159 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
160 assert.NoError(t, err)
161 assertExitAPIs(t, accountExits, fetchedExits)
162 // Get by tokenID
163 fetchedExits = []testExit{}
164 limit = 5
165 tokenID := tc.exits[0].Token.TokenID
166 path = fmt.Sprintf(
167 "%s?tokenId=%d&limit=%d",
168 endpoint, tokenID, limit,
169 )
170 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
171 assert.NoError(t, err)
172 tokenIDExits := []testExit{}
173 for i := 0; i < len(tc.exits); i++ {
174 if tc.exits[i].Token.TokenID == tokenID {
175 tokenIDExits = append(tokenIDExits, tc.exits[i])
176 }
177 }
178 assertExitAPIs(t, tokenIDExits, fetchedExits)
179 // idx
180 fetchedExits = []testExit{}
181 limit = 4
182 idx := tc.exits[0].AccountIdx
183 path = fmt.Sprintf(
184 "%s?accountIndex=%s&limit=%d",
185 endpoint, idx, limit,
186 )
187 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
188 assert.NoError(t, err)
189 idxExits := []testExit{}
190 for i := 0; i < len(tc.exits); i++ {
191 if tc.exits[i].AccountIdx[6:] == idx[6:] {
192 idxExits = append(idxExits, tc.exits[i])
193 }
194 }
195 assertExitAPIs(t, idxExits, fetchedExits)
196 // batchNum
197 fetchedExits = []testExit{}
198 limit = 3
199 batchNum := tc.exits[0].BatchNum
200 path = fmt.Sprintf(
201 "%s?batchNum=%d&limit=%d",
202 endpoint, batchNum, limit,
203 )
204 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
205 assert.NoError(t, err)
206 batchNumExits := []testExit{}
207 for i := 0; i < len(tc.exits); i++ {
208 if tc.exits[i].BatchNum == batchNum {
209 batchNumExits = append(batchNumExits, tc.exits[i])
210 }
211 }
212 assertExitAPIs(t, batchNumExits, fetchedExits)
213 // OnlyPendingWithdraws
214 fetchedExits = []testExit{}
215 limit = 7
216 path = fmt.Sprintf(
217 "%s?&onlyPendingWithdraws=%t&limit=%d",
218 endpoint, true, limit,
219 )
220 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
221 assert.NoError(t, err)
222 pendingExits := []testExit{}
223 for i := 0; i < len(tc.exits); i++ {
224 if tc.exits[i].InstantWithdrawn == nil && tc.exits[i].DelayedWithdrawn == nil {
225 pendingExits = append(pendingExits, tc.exits[i])
226 }
227 }
228 assertExitAPIs(t, pendingExits, fetchedExits)
229 // Multiple filters
230 fetchedExits = []testExit{}
231 limit = 1
232 path = fmt.Sprintf(
233 "%s?batchNum=%d&tokeId=%d&limit=%d",
234 endpoint, batchNum, tokenID, limit,
235 )
236 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
237 assert.NoError(t, err)
238 mixedExits := []testExit{}
239 flipedExits := []testExit{}
240 for i := 0; i < len(tc.exits); i++ {
241 if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
242 mixedExits = append(mixedExits, tc.exits[i])
243 }
244 flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
245 }
246 assertExitAPIs(t, mixedExits, fetchedExits)
247 // All, in reverse order
248 fetchedExits = []testExit{}
249 limit = 5
250 path = fmt.Sprintf("%s?limit=%d", endpoint, limit)
251 err = doGoodReqPaginated(path, historydb.OrderDesc, &testExitsResponse{}, appendIter)
252 assert.NoError(t, err)
253 assertExitAPIs(t, flipedExits, fetchedExits)
254 // Empty array
255 fetchedExits = []testExit{}
256 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
257 err = doGoodReqPaginated(path, historydb.OrderDesc, &testExitsResponse{}, appendIter)
258 assert.NoError(t, err)
259 assertExitAPIs(t, []testExit{}, fetchedExits)
260 // 400
261 path = fmt.Sprintf(
262 "%s?accountIndex=%s&hezEthereumAddress=%s",
263 endpoint, idx, account.EthAddr,
264 )
265 err = doBadReq("GET", path, nil, 400)
266 assert.NoError(t, err)
267 path = fmt.Sprintf("%s?tokenId=X", endpoint)
268 err = doBadReq("GET", path, nil, 400)
269 assert.NoError(t, err)
270}
271
272func TestGetExit(t *testing.T) {
273 // Get all txs by their ID
274 endpoint := apiURL + "exits/"
275 fetchedExits := []testExit{}
276 for _, exit := range tc.exits {
277 fetchedExit := testExit{}
278 assert.NoError(
279 t, doGoodReq(
280 "GET",
281 fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
282 nil, &fetchedExit,
283 ),
284 )
285 fetchedExits = append(fetchedExits, fetchedExit)
286 }
287 assertExitAPIs(t, tc.exits, fetchedExits)
288 // 400
289 err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
290 assert.NoError(t, err)
291 err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
292 assert.NoError(t, err)
293 // 404
294 err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
295 assert.NoError(t, err)
296}
297
298func assertExitAPIs(t *testing.T, expected, actual []testExit) {
299 require.Equal(t, len(expected), len(actual))
300 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
301 actual[i].ItemID = 0
302 actual[i].Token.ItemID = 0
303 if expected[i].Token.USDUpdate == nil {
304 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
305 } else {
306 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
307 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
308 }
309 assert.Equal(t, expected[i], actual[i])
310 }
311}