arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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}