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