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, tracerr.Wrap(fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", lit))
314 }
315 } else if lit == "AddToken" {
316 if err := p.expectChar(c, "("); err != nil {
317 return c, tracerr.Wrap(err)
318 }
319 _, lit = p.scanIgnoreWhitespace()
320 c.Literal += lit
321 tidI, err := strconv.Atoi(lit)
322 if err != nil {
323 line, _ := p.s.r.ReadString('\n')
324 c.Literal += line
325 return c, tracerr.Wrap(err)
326 }
327 c.TokenID = common.TokenID(tidI)
328 if err := p.expectChar(c, ")"); err != nil {
329 return c, tracerr.Wrap(err)
330 }
331 c.Typ = TypeAddToken
332 line, _ := p.s.r.ReadString('\n')
333 c.Literal += line
334 return c, newEventLine
335 }
336
337 if setType == "" {
338 return c, tracerr.Wrap(fmt.Errorf("Set type not defined"))
339 }
340 transferring := false
341 fee := false
342
343 if setType == SetTypeBlockchain {
344 switch lit {
345 case "Deposit":
346 c.Typ = common.TxTypeDeposit
347 case "Exit":
348 c.Typ = common.TxTypeExit
349 fee = true
350 case "Transfer":
351 c.Typ = common.TxTypeTransfer
352 transferring = true
353 fee = true
354 case "CreateAccountDeposit":
355 c.Typ = common.TxTypeCreateAccountDeposit
356 case "CreateAccountDepositTransfer":
357 c.Typ = common.TxTypeCreateAccountDepositTransfer
358 transferring = true
359 case "CreateAccountCoordinator":
360 c.Typ = TxTypeCreateAccountDepositCoordinator
361 // transferring is false, as the Coordinator tx transfer will be 0
362 case "DepositTransfer":
363 c.Typ = common.TxTypeDepositTransfer
364 transferring = true
365 case "ForceTransfer":
366 c.Typ = common.TxTypeForceTransfer
367 transferring = true
368 case "ForceExit":
369 c.Typ = common.TxTypeForceExit
370 default:
371 return c, tracerr.Wrap(fmt.Errorf("Unexpected Blockchain tx type: %s", lit))
372 }
373 } else if setType == SetTypePoolL2 {
374 switch lit {
375 case "PoolTransfer":
376 c.Typ = common.TxTypeTransfer
377 transferring = true
378 fee = true
379 case "PoolTransferToEthAddr":
380 c.Typ = common.TxTypeTransferToEthAddr
381 transferring = true
382 fee = true
383 case "PoolTransferToBJJ":
384 c.Typ = common.TxTypeTransferToBJJ
385 transferring = true
386 fee = true
387 case "PoolExit":
388 c.Typ = common.TxTypeExit
389 fee = true
390 default:
391 return c, tracerr.Wrap(fmt.Errorf("Unexpected PoolL2 tx type: %s", lit))
392 }
393 } else {
394 return c, tracerr.Wrap(fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", setType))
395 }
396
397 if err := p.expectChar(c, "("); err != nil {
398 return c, tracerr.Wrap(err)
399 }
400 _, lit = p.scanIgnoreWhitespace()
401 c.Literal += lit
402 tidI, err := strconv.Atoi(lit)
403 if err != nil {
404 line, _ := p.s.r.ReadString('\n')
405 c.Literal += line
406 return c, tracerr.Wrap(err)
407 }
408 c.TokenID = common.TokenID(tidI)
409 if err := p.expectChar(c, ")"); err != nil {
410 return c, tracerr.Wrap(err)
411 }
412 _, lit = p.scanIgnoreWhitespace()
413 c.Literal += lit
414 c.From = lit
415 if c.Typ == TxTypeCreateAccountDepositCoordinator {
416 line, _ := p.s.r.ReadString('\n')
417 c.Literal += line
418 return c, nil
419 }
420 _, lit = p.scanIgnoreWhitespace()
421 c.Literal += lit
422 if transferring {
423 if lit != "-" {
424 return c, tracerr.Wrap(fmt.Errorf("Expected '-', found '%s'", lit))
425 }
426 _, lit = p.scanIgnoreWhitespace()
427 c.Literal += lit
428 c.To = lit
429 _, lit = p.scanIgnoreWhitespace()
430 c.Literal += lit
431 }
432 if lit != ":" {
433 line, _ := p.s.r.ReadString('\n')
434 c.Literal += line
435 return c, tracerr.Wrap(fmt.Errorf("Expected ':', found '%s'", lit))
436 }
437 if c.Typ == common.TxTypeDepositTransfer ||
438 c.Typ == common.TxTypeCreateAccountDepositTransfer {
439 // deposit case
440 _, lit = p.scanIgnoreWhitespace()
441 c.Literal += lit
442 depositAmount, ok := new(big.Int).SetString(lit, 10)
443 if !ok {
444 line, _ := p.s.r.ReadString('\n')
445 c.Literal += line
446 return c, tracerr.Wrap(fmt.Errorf("Can not parse number for DepositAmount"))
447 }
448 c.DepositAmount = depositAmount
449 if err := p.expectChar(c, ","); err != nil {
450 return c, tracerr.Wrap(err)
451 }
452 }
453 _, lit = p.scanIgnoreWhitespace()
454 c.Literal += lit
455 amount, ok := new(big.Int).SetString(lit, 10)
456 if !ok {
457 line, _ := p.s.r.ReadString('\n')
458 c.Literal += line
459 return c, tracerr.Wrap(fmt.Errorf("Can not parse number for Amount: %s", lit))
460 }
461 if c.Typ == common.TxTypeDeposit ||
462 c.Typ == common.TxTypeCreateAccountDeposit {
463 c.DepositAmount = amount
464 } else {
465 c.Amount = amount
466 }
467 if fee {
468 if err := p.expectChar(c, "("); err != nil {
469 return c, tracerr.Wrap(err)
470 }
471 _, lit = p.scanIgnoreWhitespace()
472 c.Literal += lit
473 fee, err := strconv.Atoi(lit)
474 if err != nil {
475 line, _ := p.s.r.ReadString('\n')
476 c.Literal += line
477 return c, tracerr.Wrap(err)
478 }
479 if fee > common.MaxFeePlan-1 {
480 line, _ := p.s.r.ReadString('\n')
481 c.Literal += line
482 return c, tracerr.Wrap(fmt.Errorf("Fee %d can not be bigger than 255", fee))
483 }
484 c.Fee = uint8(fee)
485
486 if err := p.expectChar(c, ")"); err != nil {
487 return c, tracerr.Wrap(err)
488 }
489 }
490
491 if tok == EOF {
492 return nil, tracerr.Wrap(errof)
493 }
494 return c, nil
495}
496
497func (p *parser) expectChar(c *Instruction, ch string) error {
498 _, lit := p.scanIgnoreWhitespace()
499 c.Literal += lit
500 if lit != ch {
501 line, _ := p.s.r.ReadString('\n')
502 c.Literal += line
503 return tracerr.Wrap(fmt.Errorf("Expected '%s', found '%s'", ch, lit))
504 }
505 return nil
506}
507
508func idxTokenIDToString(idx string, tid common.TokenID) string {
509 return idx + strconv.Itoa(int(tid))
510}
511
512// parse parses through reader
513func (p *parser) parse() (*parsedSet, error) {
514 ps := &parsedSet{}
515 i := 0 // lines will start counting at line 1
516 users := make(map[string]bool)
517 for {
518 i++
519 instruction, err := p.parseLine(ps.typ)
520 if tracerr.Unwrap(err) == errof {
521 break
522 }
523 if tracerr.Unwrap(err) == setTypeLine {
524 if ps.typ != "" {
525 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))
526 }
527 if instruction.Typ == "PoolL2" {
528 ps.typ = SetTypePoolL2
529 } else if instruction.Typ == "Blockchain" {
530 ps.typ = SetTypeBlockchain
531 } else {
532 log.Fatalf("Line %d: Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", i, instruction.Typ)
533 }
534 continue
535 }
536 if tracerr.Unwrap(err) == commentLine {
537 continue
538 }
539 instruction.LineNum = i
540 if tracerr.Unwrap(err) == newEventLine {
541 if instruction.Typ == TypeAddToken && instruction.TokenID == common.TokenID(0) {
542 return ps, tracerr.Wrap(fmt.Errorf("Line %d: AddToken can not register TokenID 0", i))
543 }
544 ps.instructions = append(ps.instructions, *instruction)
545 continue
546 }
547 if err != nil {
548 return ps, tracerr.Wrap(fmt.Errorf("Line %d: %s, err: %s", i, instruction.Literal, err.Error()))
549 }
550 if ps.typ == "" {
551 return ps, tracerr.Wrap(fmt.Errorf("Line %d: Set type not defined", i))
552 }
553 ps.instructions = append(ps.instructions, *instruction)
554 users[instruction.From] = true
555 if instruction.Typ == common.TxTypeTransfer || instruction.Typ == common.TxTypeTransferToEthAddr || instruction.Typ == common.TxTypeTransferToBJJ { // type: Transfer
556 users[instruction.To] = true
557 }
558 }
559 for u := range users {
560 ps.users = append(ps.users, u)
561 }
562 sort.Strings(ps.users)
563 return ps, nil
564}