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