diff --git a/common/account.go b/common/account.go index 6e64a44..b6cc425 100644 --- a/common/account.go +++ b/common/account.go @@ -71,7 +71,7 @@ func (l *Account) BigInts() ([NLEAFELEMS]*big.Int, error) { // HashValue returns the value of the Account, which is the Poseidon hash of its *big.Int representation func (l *Account) HashValue() (*big.Int, error) { b0 := big.NewInt(0) - toHash := [poseidon.T]*big.Int{b0, b0, b0, b0, b0, b0} + toHash := []*big.Int{b0, b0, b0, b0, b0, b0} lBI, err := l.BigInts() if err != nil { return nil, err diff --git a/common/account_test.go b/common/account_test.go index cf6b36b..c19d0b7 100644 --- a/common/account_test.go +++ b/common/account_test.go @@ -106,7 +106,7 @@ func TestAccountHashValue(t *testing.T) { v, err := account.HashValue() assert.Nil(t, err) - assert.Equal(t, "6335844662301214382338419199835935731871537354006112711277201708185593574314", v.String()) + assert.Equal(t, "16085711911723375585301279875451049849443101031421093098714359651259271023730", v.String()) } func TestAccountErrNotInFF(t *testing.T) { diff --git a/go.mod b/go.mod index 3c8a666..33a2888 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gobuffalo/packr/v2 v2.8.0 github.com/iden3/go-iden3-core v0.0.8 - github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8 - github.com/iden3/go-merkletree v0.0.0-20200815144208-1f1bd54b93ae + github.com/iden3/go-iden3-crypto v0.0.6-0.20200819064831-09d161e9f670 + github.com/iden3/go-merkletree v0.0.0-20200819092443-dc656fdd32fc github.com/jinzhu/gorm v1.9.15 github.com/jmoiron/sqlx v1.2.0 github.com/lib/pq v1.8.0 diff --git a/go.sum b/go.sum index ff62838..2e6a56f 100644 --- a/go.sum +++ b/go.sum @@ -274,6 +274,8 @@ github.com/iden3/go-iden3-crypto v0.0.6-0.20200813145745-66519124ca17 h1:aqy++85 github.com/iden3/go-iden3-crypto v0.0.6-0.20200813145745-66519124ca17/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8 h1:1QeVvr/DjiBpk8tw3vBfYD+zs0JeYl3fPIUA/foDq3w= github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= +github.com/iden3/go-iden3-crypto v0.0.6-0.20200819064831-09d161e9f670 h1:gNBFu/WnRfNn+xywE04fgCWSHlb6wr0nIIll9i4R2fc= +github.com/iden3/go-iden3-crypto v0.0.6-0.20200819064831-09d161e9f670/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3 h1:QR6LqG1HqqPE4myiLR73QFIieAfwODG3bqo1juuaqVI= github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME= github.com/iden3/go-merkletree v0.0.0-20200806171216-dd600560e44c h1:EzVMSVkwKdfcOR1a+rZe9dfbtAj6KdJnBxOEZ5B+gCQ= @@ -284,6 +286,10 @@ github.com/iden3/go-merkletree v0.0.0-20200815105542-2277604e65dd h1:AkPlODLWkgQ github.com/iden3/go-merkletree v0.0.0-20200815105542-2277604e65dd/go.mod h1:/MsQOzDnxK8X/u7XP9ZBoZwZ4gIm9FlwfqckH5dRuTM= github.com/iden3/go-merkletree v0.0.0-20200815144208-1f1bd54b93ae h1:CC8oKPM+38/Dkq20QymuIjyRo0UFzJZeH5DPbGWURrI= github.com/iden3/go-merkletree v0.0.0-20200815144208-1f1bd54b93ae/go.mod h1:/MsQOzDnxK8X/u7XP9ZBoZwZ4gIm9FlwfqckH5dRuTM= +github.com/iden3/go-merkletree v0.0.0-20200819075941-77df3096f7ea h1:0MtMnN42ULNxOnJYa0FH1qjeSEBPkYVH6ZCs22rz2FU= +github.com/iden3/go-merkletree v0.0.0-20200819075941-77df3096f7ea/go.mod h1:MRe6i0mi2oDVUzgBIHsNRE6XAg8EBuqIQZMsd+do+dU= +github.com/iden3/go-merkletree v0.0.0-20200819092443-dc656fdd32fc h1:VnRP7JCp5TJHnTC+r8NrXGkRBvp62tRGqeA3FcdEq+Q= +github.com/iden3/go-merkletree v0.0.0-20200819092443-dc656fdd32fc/go.mod h1:MRe6i0mi2oDVUzgBIHsNRE6XAg8EBuqIQZMsd+do+dU= github.com/iden3/go-wasm3 v0.0.1/go.mod h1:j+TcAB94Dfrjlu5kJt83h2OqAU+oyNUTwNZnQyII1sI= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= diff --git a/test/lang.go b/test/lang.go new file mode 100644 index 0000000..444c793 --- /dev/null +++ b/test/lang.go @@ -0,0 +1,304 @@ +package test + +import ( + "bufio" + "bytes" + "fmt" + "io" + "sort" + "strconv" + + "github.com/hermeznetwork/hermez-node/common" +) + +var eof = rune(0) +var errof = fmt.Errorf("eof in parseline") + +const ( + ILLEGAL Token = iota + WS + EOF + + IDENT // val +) + +type Instruction struct { + Literal string + From string + To string + Amount uint64 + TokenID common.TokenID + Type int // 0: Deposit, 1: Transfer +} + +type Instructions struct { + Instructions []*Instruction + Accounts []string + TokenIDs []common.TokenID +} + +func (i Instruction) String() string { + buf := bytes.NewBufferString("") + switch i.Type { + case 0: + fmt.Fprintf(buf, "Type: Deposit, ") + case 1: + fmt.Fprintf(buf, "Type: Transfer, ") + default: + } + fmt.Fprintf(buf, "From: %s, ", i.From) + if i.Type == 1 { + fmt.Fprintf(buf, "To: %s, ", i.To) + } + fmt.Fprintf(buf, "Amount: %d, ", i.Amount) + fmt.Fprintf(buf, "TokenID: %d,\n", i.TokenID) + return buf.String() +} + +func (i Instruction) Raw() string { + buf := bytes.NewBufferString("") + fmt.Fprintf(buf, "%s", i.From) + if i.Type == 1 { + fmt.Fprintf(buf, "-%s", i.To) + } + fmt.Fprintf(buf, " (%d):", i.TokenID) + fmt.Fprintf(buf, " %d", i.Amount) + return buf.String() +} + +type Token int + +type Scanner struct { + r *bufio.Reader +} + +func isWhitespace(ch rune) bool { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f' +} + +func isLetter(ch rune) bool { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') +} + +func isDigit(ch rune) bool { + return (ch >= '0' && ch <= '9') +} + +// NewScanner creates a new Scanner with the given io.Reader +func NewScanner(r io.Reader) *Scanner { + return &Scanner{r: bufio.NewReader(r)} +} + +func (s *Scanner) read() rune { + ch, _, err := s.r.ReadRune() + if err != nil { + return eof + } + return ch +} + +func (s *Scanner) unread() { + _ = s.r.UnreadRune() +} + +// scan returns the Token and literal string of the current value +func (s *Scanner) scan() (tok Token, lit string) { + ch := s.read() + + if isWhitespace(ch) { + // space + s.unread() + return s.scanWhitespace() + } else if isLetter(ch) || isDigit(ch) { + // letter/digit + s.unread() + return s.scanIndent() + } + + if ch == eof { + return EOF, "" + } + + return ILLEGAL, string(ch) +} + +func (s *Scanner) scanWhitespace() (token Token, lit string) { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.read(); ch == eof { + break + } else if !isWhitespace(ch) { + s.unread() + break + } else { + _, _ = buf.WriteRune(ch) + } + } + return WS, buf.String() +} + +func (s *Scanner) scanIndent() (tok Token, lit string) { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.read(); ch == eof { + break + } else if !isLetter(ch) && !isDigit(ch) { + s.unread() + break + } else { + _, _ = buf.WriteRune(ch) + } + } + + if len(buf.String()) == 1 { + return Token(rune(buf.String()[0])), buf.String() + } + return IDENT, buf.String() +} + +// Parser defines the parser +type Parser struct { + s *Scanner + buf struct { + tok Token + lit string + n int + } +} + +// NewParser creates a new parser from a io.Reader +func NewParser(r io.Reader) *Parser { + return &Parser{s: NewScanner(r)} +} + +func (p *Parser) scan() (tok Token, lit string) { + // if there is a token in the buffer return it + if p.buf.n != 0 { + p.buf.n = 0 + return p.buf.tok, p.buf.lit + } + tok, lit = p.s.scan() + + p.buf.tok, p.buf.lit = tok, lit + + return +} + +func (p *Parser) scanIgnoreWhitespace() (tok Token, lit string) { + tok, lit = p.scan() + if tok == WS { + tok, lit = p.scan() + } + return +} + +// parseLine parses the current line +func (p *Parser) parseLine() (*Instruction, error) { + /* + line can be Deposit: + A (1): 10 + or Transfer: + A-B (1): 6 + */ + c := &Instruction{} + tok, lit := p.scanIgnoreWhitespace() + if tok == EOF { + return nil, errof + } + c.Literal += lit + c.From = lit + + _, lit = p.scanIgnoreWhitespace() + c.Literal += lit + if lit == "-" { + // transfer + _, lit = p.scanIgnoreWhitespace() + c.Literal += lit + c.To = lit + c.Type = 1 + _, lit = p.scanIgnoreWhitespace() // expect ( + c.Literal += lit + if lit != "(" { + line, _ := p.s.r.ReadString('\n') + c.Literal += line + return c, fmt.Errorf("Expected '(', found '%s'", lit) + } + } else { + c.Type = 0 + } + + _, lit = p.scanIgnoreWhitespace() + c.Literal += lit + tidI, err := strconv.Atoi(lit) + if err != nil { + line, _ := p.s.r.ReadString('\n') + c.Literal += line + return c, err + } + c.TokenID = common.TokenID(tidI) + _, lit = p.scanIgnoreWhitespace() // expect ) + c.Literal += lit + if lit != ")" { + line, _ := p.s.r.ReadString('\n') + c.Literal += line + return c, fmt.Errorf("Expected ')', found '%s'", lit) + } + + _, lit = p.scanIgnoreWhitespace() // expect : + c.Literal += lit + if lit != ":" { + line, _ := p.s.r.ReadString('\n') + c.Literal += line + return c, fmt.Errorf("Expected ':', found '%s'", lit) + } + tok, lit = p.scanIgnoreWhitespace() + c.Literal += lit + amount, err := strconv.Atoi(lit) + if err != nil { + line, _ := p.s.r.ReadString('\n') + c.Literal += line + return c, err + } + c.Amount = uint64(amount) + + if tok == EOF { + return nil, errof + } + return c, nil +} + +// Parse parses through reader +func (p *Parser) Parse() (Instructions, error) { + var instructions Instructions + i := 0 + accounts := make(map[string]bool) + tokenids := make(map[common.TokenID]bool) + for { + instruction, err := p.parseLine() + if err == errof { + break + } + if err != nil { + return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.Literal, err.Error()) + } + instructions.Instructions = append(instructions.Instructions, instruction) + accounts[instruction.From] = true + if instruction.Type == 1 { // type: Transfer + accounts[instruction.To] = true + } + tokenids[instruction.TokenID] = true + i++ + } + for a := range accounts { + instructions.Accounts = append(instructions.Accounts, a) + } + sort.Strings(instructions.Accounts) + for tid := range tokenids { + instructions.TokenIDs = append(instructions.TokenIDs, tid) + } + return instructions, nil +} diff --git a/test/lang_test.go b/test/lang_test.go new file mode 100644 index 0000000..56528ca --- /dev/null +++ b/test/lang_test.go @@ -0,0 +1,62 @@ +package test + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +var debug = false + +func TestParse(t *testing.T) { + s := ` + A (1): 10 + A (2): 20 + B (1): 5 + A-B (1): 6 + B-C (1): 3 + C-A (1): 3 + A-B (2): 15 + User0 (1): 20 + User1 (3) : 20 + User0-User1 (1): 15 + User1-User0 (3): 15 + ` + parser := NewParser(strings.NewReader(s)) + instructions, err := parser.Parse() + assert.Nil(t, err) + assert.Equal(t, 11, len(instructions.Instructions)) + assert.Equal(t, 5, len(instructions.Accounts)) + assert.Equal(t, 3, len(instructions.TokenIDs)) + + if debug { + fmt.Println(instructions) + for _, instruction := range instructions.Instructions { + fmt.Println(instruction.Raw()) + } + } + + assert.Equal(t, "User0 (1): 20", instructions.Instructions[7].Raw()) + assert.Equal(t, "Type: Deposit, From: User0, Amount: 20, TokenID: 1,\n", instructions.Instructions[7].String()) + assert.Equal(t, "User0-User1 (1): 15", instructions.Instructions[9].Raw()) + assert.Equal(t, "Type: Transfer, From: User0, To: User1, Amount: 15, TokenID: 1,\n", instructions.Instructions[9].String()) +} + +func TestParseErrors(t *testing.T) { + s := "A (1):: 10" + parser := NewParser(strings.NewReader(s)) + _, err := parser.Parse() + assert.Equal(t, "error parsing line 0: A(1):: 10, err: strconv.Atoi: parsing \":\": invalid syntax", err.Error()) + + s = "A (1): 10 20" + parser = NewParser(strings.NewReader(s)) + _, err = parser.Parse() + assert.Equal(t, "error parsing line 1: 20, err: strconv.Atoi: parsing \"\": invalid syntax", err.Error()) + + s = "A B (1): 10" + parser = NewParser(strings.NewReader(s)) + _, err = parser.Parse() + assert.Equal(t, "error parsing line 0: AB(1): 10, err: strconv.Atoi: parsing \"(\": invalid syntax", err.Error()) +}