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