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