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