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