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