hermez-node/test/til/lang.go
test/til/lang.goRaw
1package til
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "io"
8 "math/big"
9 "sort"
10 "strconv"
11
12 "github.com/hermeznetwork/hermez-node/common"
13 "github.com/hermeznetwork/hermez-node/log"
14 "github.com/hermeznetwork/tracerr"
15)
16
17var eof = rune(0)
18var errof = fmt.Errorf("eof in parseline")
19var commentLine = fmt.Errorf("comment in parseline") //nolint:golint
20var newEventLine = fmt.Errorf("newEventLine") //nolint:golint
21var setTypeLine = fmt.Errorf("setTypeLine") //nolint:golint
22
23// setType defines the type of the set
24type setType string
25
26// setTypeBlockchain defines the type 'Blockchain' of the set
27var setTypeBlockchain = setType("Blockchain")
28
29// setTypePoolL2 defines the type 'PoolL2' of the set
30var setTypePoolL2 = setType("PoolL2")
31
32// typeNewBatch is used for testing purposes only, and represents the
33// common.TxType of a new batch
34var typeNewBatch common.TxType = "InstrTypeNewBatch"
35
36// typeNewBatchL1 is used for testing purposes only, and represents the
37// common.TxType of a new batch
38var typeNewBatchL1 common.TxType = "InstrTypeNewBatchL1"
39
40// typeNewBlock is used for testing purposes only, and represents the
41// common.TxType of a new ethereum block
42var typeNewBlock common.TxType = "InstrTypeNewBlock"
43
44// typeAddToken is used for testing purposes only, and represents the
45// common.TxType of a new Token regsitration
46// It has 'nolint:gosec' as the string 'Token' triggers gosec as a potential leaked Token (which is not the case)
47var typeAddToken common.TxType = "InstrTypeAddToken" //nolint:gosec
48
49var txTypeCreateAccountDepositCoordinator common.TxType = "TypeCreateAccountDepositCoordinator"
50
51//nolint
52const (
53 ILLEGAL token = iota
54 WS
55 EOF
56
57 IDENT // val
58)
59
60// instruction is the data structure that represents one line of code
61type instruction struct {
62 lineNum int
63 literal string
64 from string
65 to string
66 amount *big.Int
67 loadAmount *big.Int
68 fee uint8
69 tokenID common.TokenID
70 typ common.TxType // D: Deposit, T: Transfer, E: ForceExit
71}
72
73// parsedSet contains the full Set of Instructions representing a full code
74type parsedSet struct {
75 typ setType
76 instructions []instruction
77 users []string
78}
79
80func (i instruction) String() string {
81 buf := bytes.NewBufferString("")
82 fmt.Fprintf(buf, "Type: %s, ", i.typ)
83 fmt.Fprintf(buf, "From: %s, ", i.from)
84 if i.typ == common.TxTypeTransfer ||
85 i.typ == common.TxTypeDepositTransfer ||
86 i.typ == common.TxTypeCreateAccountDepositTransfer {
87 fmt.Fprintf(buf, "To: %s, ", i.to)
88 }
89
90 if i.typ == common.TxTypeDeposit ||
91 i.typ == common.TxTypeDepositTransfer ||
92 i.typ == common.TxTypeCreateAccountDepositTransfer {
93 fmt.Fprintf(buf, "LoadAmount: %d, ", i.loadAmount)
94 }
95 if i.typ != common.TxTypeDeposit {
96 fmt.Fprintf(buf, "Amount: %d, ", i.amount)
97 }
98 if i.typ == common.TxTypeTransfer ||
99 i.typ == common.TxTypeDepositTransfer ||
100 i.typ == common.TxTypeCreateAccountDepositTransfer {
101 fmt.Fprintf(buf, "Fee: %d, ", i.fee)
102 }
103 fmt.Fprintf(buf, "TokenID: %d\n", i.tokenID)
104 return buf.String()
105}
106
107// Raw returns a string with the raw representation of the Instruction
108func (i instruction) raw() string {
109 buf := bytes.NewBufferString("")
110 fmt.Fprintf(buf, "%s", i.typ)
111 fmt.Fprintf(buf, "(%d)", i.tokenID)
112 fmt.Fprintf(buf, "%s", i.from)
113 if i.typ == common.TxTypeTransfer ||
114 i.typ == common.TxTypeDepositTransfer ||
115 i.typ == common.TxTypeCreateAccountDepositTransfer {
116 fmt.Fprintf(buf, "-%s", i.to)
117 }
118 fmt.Fprintf(buf, ":")
119 if i.typ == common.TxTypeDeposit ||
120 i.typ == common.TxTypeDepositTransfer ||
121 i.typ == common.TxTypeCreateAccountDepositTransfer {
122 fmt.Fprintf(buf, "%d", i.loadAmount)
123 }
124 if i.typ != common.TxTypeDeposit {
125 fmt.Fprintf(buf, "%d", i.amount)
126 }
127 if i.typ == common.TxTypeTransfer {
128 fmt.Fprintf(buf, "(%d)", i.fee)
129 }
130 return buf.String()
131}
132
133type token int
134
135type scanner struct {
136 r *bufio.Reader
137}
138
139func isWhitespace(ch rune) bool {
140 return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
141}
142
143func isLetter(ch rune) bool {
144 return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
145}
146
147func isComment(ch rune) bool {
148 return ch == '/'
149}
150
151func isDigit(ch rune) bool {
152 return (ch >= '0' && ch <= '9')
153}
154
155// newScanner creates a new scanner with the given io.Reader
156func newScanner(r io.Reader) *scanner {
157 return &scanner{r: bufio.NewReader(r)}
158}
159
160func (s *scanner) read() rune {
161 ch, _, err := s.r.ReadRune()
162 if err != nil {
163 return eof
164 }
165 return ch
166}
167
168func (s *scanner) unread() {
169 _ = s.r.UnreadRune()
170}
171
172// scan returns the token and literal string of the current value
173func (s *scanner) scan() (tok token, lit string) {
174 ch := s.read()
175
176 if isWhitespace(ch) {
177 // space
178 s.unread()
179 return s.scanWhitespace()
180 } else if isLetter(ch) || isDigit(ch) {
181 // letter/digit
182 s.unread()
183 return s.scanIndent()
184 } else if isComment(ch) {
185 // comment
186 s.unread()
187 return s.scanIndent()
188 }
189
190 if ch == eof {
191 return EOF, ""
192 }
193
194 return ILLEGAL, string(ch)
195}
196
197func (s *scanner) scanWhitespace() (token token, lit string) {
198 var buf bytes.Buffer
199 buf.WriteRune(s.read())
200
201 for {
202 if ch := s.read(); ch == eof {
203 break
204 } else if !isWhitespace(ch) {
205 s.unread()
206 break
207 } else {
208 _, _ = buf.WriteRune(ch)
209 }
210 }
211 return WS, buf.String()
212}
213
214func (s *scanner) scanIndent() (tok token, lit string) {
215 var buf bytes.Buffer
216 buf.WriteRune(s.read())
217
218 for {
219 if ch := s.read(); ch == eof {
220 break
221 } else if !isLetter(ch) && !isDigit(ch) {
222 s.unread()
223 break
224 } else {
225 _, _ = buf.WriteRune(ch)
226 }
227 }
228
229 if len(buf.String()) == 1 {
230 return token(rune(buf.String()[0])), buf.String()
231 }
232 return IDENT, buf.String()
233}
234
235// parser defines the parser
236type parser struct {
237 s *scanner
238 buf struct {
239 tok token
240 lit string
241 n int
242 }
243}
244
245// newParser creates a new parser from a io.Reader
246func newParser(r io.Reader) *parser {
247 return &parser{s: newScanner(r)}
248}
249
250func (p *parser) scan() (tok token, lit string) {
251 // if there is a token in the buffer return it
252 if p.buf.n != 0 {
253 p.buf.n = 0
254 return p.buf.tok, p.buf.lit
255 }
256 tok, lit = p.s.scan()
257
258 p.buf.tok, p.buf.lit = tok, lit
259
260 return
261}
262
263func (p *parser) scanIgnoreWhitespace() (tok token, lit string) {
264 tok, lit = p.scan()
265 if tok == WS {
266 tok, lit = p.scan()
267 }
268 return
269}
270
271// parseLine parses the current line
272func (p *parser) parseLine(setType setType) (*instruction, error) {
273 c := &instruction{}
274 tok, lit := p.scanIgnoreWhitespace()
275 if tok == EOF {
276 return nil, tracerr.Wrap(errof)
277 }
278 c.literal += lit
279 if lit == "/" {
280 _, _ = p.s.r.ReadString('\n')
281 return nil, commentLine
282 } else if lit == ">" {
283 if setType == setTypePoolL2 {
284 return c, tracerr.Wrap(fmt.Errorf("Unexpected '>' at PoolL2Txs set"))
285 }
286 _, lit = p.scanIgnoreWhitespace()
287 if lit == "batch" {
288 _, _ = p.s.r.ReadString('\n')
289 return &instruction{typ: typeNewBatch}, newEventLine
290 } else if lit == "batchL1" {
291 _, _ = p.s.r.ReadString('\n')
292 return &instruction{typ: typeNewBatchL1}, newEventLine
293 } else if lit == "block" {
294 _, _ = p.s.r.ReadString('\n')
295 return &instruction{typ: typeNewBlock}, newEventLine
296 } else {
297 return c, tracerr.Wrap(fmt.Errorf("Unexpected '> %s', expected '> batch' or '> block'", lit))
298 }
299 } else if lit == "Type" {
300 if err := p.expectChar(c, ":"); err != nil {
301 return c, tracerr.Wrap(err)
302 }
303 _, lit = p.scanIgnoreWhitespace()
304 if lit == "Blockchain" {
305 return &instruction{typ: "Blockchain"}, setTypeLine
306 } else if lit == "PoolL2" {
307 return &instruction{typ: "PoolL2"}, setTypeLine
308 } else {
309 return c, tracerr.Wrap(fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", lit))
310 }
311 } else if lit == "AddToken" {
312 if err := p.expectChar(c, "("); err != nil {
313 return c, tracerr.Wrap(err)
314 }
315 _, lit = p.scanIgnoreWhitespace()
316 c.literal += lit
317 tidI, err := strconv.Atoi(lit)
318 if err != nil {
319 line, _ := p.s.r.ReadString('\n')
320 c.literal += line
321 return c, tracerr.Wrap(err)
322 }
323 c.tokenID = common.TokenID(tidI)
324 if err := p.expectChar(c, ")"); err != nil {
325 return c, tracerr.Wrap(err)
326 }
327 c.typ = typeAddToken
328 line, _ := p.s.r.ReadString('\n')
329 c.literal += line
330 return c, newEventLine
331 }
332
333 if setType == "" {
334 return c, tracerr.Wrap(fmt.Errorf("Set type not defined"))
335 }
336 transferring := false
337 fee := false
338
339 if setType == setTypeBlockchain {
340 switch lit {
341 case "Deposit":
342 c.typ = common.TxTypeDeposit
343 case "Exit":
344 c.typ = common.TxTypeExit
345 fee = true
346 case "Transfer":
347 c.typ = common.TxTypeTransfer
348 transferring = true
349 fee = true
350 case "CreateAccountDeposit":
351 c.typ = common.TxTypeCreateAccountDeposit
352 case "CreateAccountDepositTransfer":
353 c.typ = common.TxTypeCreateAccountDepositTransfer
354 transferring = true
355 case "CreateAccountCoordinator":
356 c.typ = txTypeCreateAccountDepositCoordinator
357 // transferring is false, as the Coordinator tx transfer will be 0
358 case "DepositTransfer":
359 c.typ = common.TxTypeDepositTransfer
360 transferring = true
361 case "ForceTransfer":
362 c.typ = common.TxTypeForceTransfer
363 transferring = true
364 case "ForceExit":
365 c.typ = common.TxTypeForceExit
366 default:
367 return c, tracerr.Wrap(fmt.Errorf("Unexpected Blockchain tx type: %s", lit))
368 }
369 } else if setType == setTypePoolL2 {
370 switch lit {
371 case "PoolTransfer":
372 c.typ = common.TxTypeTransfer
373 transferring = true
374 fee = true
375 case "PoolTransferToEthAddr":
376 c.typ = common.TxTypeTransferToEthAddr
377 transferring = true
378 fee = true
379 case "PoolTransferToBJJ":
380 c.typ = common.TxTypeTransferToBJJ
381 transferring = true
382 fee = true
383 case "PoolExit":
384 c.typ = common.TxTypeExit
385 fee = true
386 default:
387 return c, tracerr.Wrap(fmt.Errorf("Unexpected PoolL2 tx type: %s", lit))
388 }
389 } else {
390 return c, tracerr.Wrap(fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", setType))
391 }
392
393 if err := p.expectChar(c, "("); err != nil {
394 return c, tracerr.Wrap(err)
395 }
396 _, lit = p.scanIgnoreWhitespace()
397 c.literal += lit
398 tidI, err := strconv.Atoi(lit)
399 if err != nil {
400 line, _ := p.s.r.ReadString('\n')
401 c.literal += line
402 return c, tracerr.Wrap(err)
403 }
404 c.tokenID = common.TokenID(tidI)
405 if err := p.expectChar(c, ")"); err != nil {
406 return c, tracerr.Wrap(err)
407 }
408 _, lit = p.scanIgnoreWhitespace()
409 c.literal += lit
410 c.from = lit
411 if c.typ == txTypeCreateAccountDepositCoordinator {
412 line, _ := p.s.r.ReadString('\n')
413 c.literal += line
414 return c, nil
415 }
416 _, lit = p.scanIgnoreWhitespace()
417 c.literal += lit
418 if transferring {
419 if lit != "-" {
420 return c, tracerr.Wrap(fmt.Errorf("Expected '-', found '%s'", lit))
421 }
422 _, lit = p.scanIgnoreWhitespace()
423 c.literal += lit
424 c.to = lit
425 _, lit = p.scanIgnoreWhitespace()
426 c.literal += lit
427 }
428 if lit != ":" {
429 line, _ := p.s.r.ReadString('\n')
430 c.literal += line
431 return c, tracerr.Wrap(fmt.Errorf("Expected ':', found '%s'", lit))
432 }
433 if c.typ == common.TxTypeDepositTransfer ||
434 c.typ == common.TxTypeCreateAccountDepositTransfer {
435 // deposit case
436 _, lit = p.scanIgnoreWhitespace()
437 c.literal += lit
438 loadAmount, ok := new(big.Int).SetString(lit, 10)
439 if !ok {
440 line, _ := p.s.r.ReadString('\n')
441 c.literal += line
442 return c, tracerr.Wrap(fmt.Errorf("Can not parse number for LoadAmount"))
443 }
444 c.loadAmount = loadAmount
445 if err := p.expectChar(c, ","); err != nil {
446 return c, tracerr.Wrap(err)
447 }
448 }
449 _, lit = p.scanIgnoreWhitespace()
450 c.literal += lit
451 amount, ok := new(big.Int).SetString(lit, 10)
452 if !ok {
453 line, _ := p.s.r.ReadString('\n')
454 c.literal += line
455 return c, tracerr.Wrap(fmt.Errorf("Can not parse number for Amount: %s", lit))
456 }
457 if c.typ == common.TxTypeDeposit ||
458 c.typ == common.TxTypeCreateAccountDeposit {
459 c.loadAmount = amount
460 } else {
461 c.amount = amount
462 }
463 if fee {
464 if err := p.expectChar(c, "("); err != nil {
465 return c, tracerr.Wrap(err)
466 }
467 _, lit = p.scanIgnoreWhitespace()
468 c.literal += lit
469 fee, err := strconv.Atoi(lit)
470 if err != nil {
471 line, _ := p.s.r.ReadString('\n')
472 c.literal += line
473 return c, tracerr.Wrap(err)
474 }
475 if fee > common.MaxFeePlan-1 {
476 line, _ := p.s.r.ReadString('\n')
477 c.literal += line
478 return c, tracerr.Wrap(fmt.Errorf("Fee %d can not be bigger than 255", fee))
479 }
480 c.fee = uint8(fee)
481
482 if err := p.expectChar(c, ")"); err != nil {
483 return c, tracerr.Wrap(err)
484 }
485 }
486
487 if tok == EOF {
488 return nil, tracerr.Wrap(errof)
489 }
490 return c, nil
491}
492
493func (p *parser) expectChar(c *instruction, ch string) error {
494 _, lit := p.scanIgnoreWhitespace()
495 c.literal += lit
496 if lit != ch {
497 line, _ := p.s.r.ReadString('\n')
498 c.literal += line
499 return tracerr.Wrap(fmt.Errorf("Expected '%s', found '%s'", ch, lit))
500 }
501 return nil
502}
503
504func idxTokenIDToString(idx string, tid common.TokenID) string {
505 return idx + strconv.Itoa(int(tid))
506}
507
508// parse parses through reader
509func (p *parser) parse() (*parsedSet, error) {
510 ps := &parsedSet{}
511 i := 0 // lines will start counting at line 1
512 users := make(map[string]bool)
513 for {
514 i++
515 instruction, err := p.parseLine(ps.typ)
516 if tracerr.Unwrap(err) == errof {
517 break
518 }
519 if tracerr.Unwrap(err) == setTypeLine {
520 if ps.typ != "" {
521 return ps, tracerr.Wrap(fmt.Errorf("Line %d: Instruction of 'Type: %s' when there is already a previous instruction 'Type: %s' defined", i, instruction.typ, ps.typ))
522 }
523 if instruction.typ == "PoolL2" {
524 ps.typ = setTypePoolL2
525 } else if instruction.typ == "Blockchain" {
526 ps.typ = setTypeBlockchain
527 } else {
528 log.Fatalf("Line %d: Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", i, instruction.typ)
529 }
530 continue
531 }
532 if tracerr.Unwrap(err) == commentLine {
533 continue
534 }
535 instruction.lineNum = i
536 if tracerr.Unwrap(err) == newEventLine {
537 if instruction.typ == typeAddToken && instruction.tokenID == common.TokenID(0) {
538 return ps, tracerr.Wrap(fmt.Errorf("Line %d: AddToken can not register TokenID 0", i))
539 }
540 ps.instructions = append(ps.instructions, *instruction)
541 continue
542 }
543 if err != nil {
544 return ps, tracerr.Wrap(fmt.Errorf("Line %d: %s, err: %s", i, instruction.literal, err.Error()))
545 }
546 if ps.typ == "" {
547 return ps, tracerr.Wrap(fmt.Errorf("Line %d: Set type not defined", i))
548 }
549 ps.instructions = append(ps.instructions, *instruction)
550 users[instruction.from] = true
551 if instruction.typ == common.TxTypeTransfer || instruction.typ == common.TxTypeTransferToEthAddr || instruction.typ == common.TxTypeTransferToBJJ { // type: Transfer
552 users[instruction.to] = true
553 }
554 }
555 for u := range users {
556 ps.users = append(ps.users, u)
557 }
558 sort.Strings(ps.users)
559 return ps, nil
560}