Skip to content

Commit 5e99b74

Browse files
committed
feat(stylus): implement event signature parser
Codex did this.
1 parent 991d7ca commit 5e99b74

File tree

2 files changed

+377
-0
lines changed

2 files changed

+377
-0
lines changed

internal/stylus/event_signature.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package stylus
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"unicode"
7+
)
8+
9+
type eventSignature struct {
10+
Name string
11+
Params []eventParameter
12+
}
13+
14+
type eventParameter struct {
15+
Type string
16+
Name string
17+
Indexed bool
18+
}
19+
20+
func parseEventSignature(signature string) (eventSignature, error) {
21+
signature = strings.TrimSpace(signature)
22+
if signature == "" {
23+
return eventSignature{}, fmt.Errorf("empty signature")
24+
}
25+
26+
if strings.HasPrefix(signature, "event") {
27+
afterKeyword := signature[len("event"):]
28+
if afterKeyword == "" {
29+
return eventSignature{}, fmt.Errorf("event signature missing identifier and parameters")
30+
}
31+
32+
trimmed := strings.TrimLeftFunc(afterKeyword, unicode.IsSpace)
33+
if len(trimmed) != len(afterKeyword) {
34+
signature = strings.TrimSpace(trimmed)
35+
}
36+
}
37+
38+
if strings.HasSuffix(signature, ";") {
39+
signature = strings.TrimSpace(signature[:len(signature)-1])
40+
}
41+
42+
if signature == "" {
43+
return eventSignature{}, fmt.Errorf("event signature missing identifier and parameters")
44+
}
45+
46+
open := strings.Index(signature, "(")
47+
close := strings.LastIndex(signature, ")")
48+
if open == -1 || close == -1 || close < open {
49+
return eventSignature{}, fmt.Errorf("invalid event signature format: %s", signature)
50+
}
51+
52+
name := strings.TrimSpace(signature[:open])
53+
if name == "" {
54+
return eventSignature{}, fmt.Errorf("event name missing in signature: %s", signature)
55+
}
56+
57+
paramsRaw := signature[open+1 : close]
58+
paramStrings, err := splitEventParameters(paramsRaw)
59+
if err != nil {
60+
return eventSignature{}, err
61+
}
62+
63+
params := make([]eventParameter, 0, len(paramStrings))
64+
for idx, paramString := range paramStrings {
65+
paramString = strings.TrimSpace(paramString)
66+
if paramString == "" {
67+
continue
68+
}
69+
70+
param, err := parseEventParameter(paramString)
71+
if err != nil {
72+
return eventSignature{}, fmt.Errorf("parse param %d: %w", idx, err)
73+
}
74+
params = append(params, param)
75+
}
76+
77+
return eventSignature{Name: name, Params: params}, nil
78+
}
79+
80+
func splitEventParameters(params string) ([]string, error) {
81+
if strings.TrimSpace(params) == "" {
82+
return nil, nil
83+
}
84+
85+
result := []string{}
86+
depth := 0
87+
last := 0
88+
for i, r := range params {
89+
switch r {
90+
case '(':
91+
depth++
92+
case ')':
93+
if depth == 0 {
94+
return nil, fmt.Errorf("unbalanced parentheses in parameter list: %s", params)
95+
}
96+
depth--
97+
case ',':
98+
if depth == 0 {
99+
segment := strings.TrimSpace(params[last:i])
100+
result = append(result, segment)
101+
last = i + 1
102+
}
103+
}
104+
}
105+
106+
if depth != 0 {
107+
return nil, fmt.Errorf("unbalanced parentheses in parameter list: %s", params)
108+
}
109+
110+
finalSegment := strings.TrimSpace(params[last:])
111+
if finalSegment != "" {
112+
result = append(result, finalSegment)
113+
}
114+
115+
return result, nil
116+
}
117+
118+
func parseEventParameter(param string) (eventParameter, error) {
119+
fields := strings.Fields(param)
120+
if len(fields) == 0 {
121+
return eventParameter{}, fmt.Errorf("empty parameter")
122+
}
123+
124+
result := eventParameter{Type: fields[0]}
125+
idx := 1
126+
127+
if idx < len(fields) && fields[idx] == "payable" {
128+
result.Type += " payable"
129+
idx++
130+
}
131+
132+
for idx < len(fields) && fields[idx] == "indexed" {
133+
result.Indexed = true
134+
idx++
135+
}
136+
137+
if idx < len(fields) {
138+
result.Name = fields[idx]
139+
idx++
140+
}
141+
142+
if idx < len(fields) {
143+
return eventParameter{}, fmt.Errorf("unexpected tokens in parameter: %s", param)
144+
}
145+
146+
return result, nil
147+
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package stylus
2+
3+
import (
4+
"reflect"
5+
"strings"
6+
"testing"
7+
)
8+
9+
func TestParseEventSignatureBasicIndexed(t *testing.T) {
10+
got, err := parseEventSignature("event Transfer(address indexed from, address indexed to, uint256 value);")
11+
if err != nil {
12+
t.Fatalf("unexpected error: %v", err)
13+
}
14+
15+
want := eventSignature{
16+
Name: "Transfer",
17+
Params: []eventParameter{
18+
{Type: "address", Name: "from", Indexed: true},
19+
{Type: "address", Name: "to", Indexed: true},
20+
{Type: "uint256", Name: "value"},
21+
},
22+
}
23+
24+
assertEventSignatureEqual(t, got, want)
25+
}
26+
27+
func TestParseEventSignatureTupleParam(t *testing.T) {
28+
got, err := parseEventSignature("event Complex(tuple(uint256,string) indexed meta, uint8 flag);")
29+
if err != nil {
30+
t.Fatalf("unexpected error: %v", err)
31+
}
32+
33+
want := eventSignature{
34+
Name: "Complex",
35+
Params: []eventParameter{
36+
{Type: "tuple(uint256,string)", Name: "meta", Indexed: true},
37+
{Type: "uint8", Name: "flag"},
38+
},
39+
}
40+
41+
assertEventSignatureEqual(t, got, want)
42+
}
43+
44+
func TestParseEventSignatureIndexedWithoutName(t *testing.T) {
45+
got, err := parseEventSignature("event Anonymous(uint256 indexed);")
46+
if err != nil {
47+
t.Fatalf("unexpected error: %v", err)
48+
}
49+
50+
want := eventSignature{
51+
Name: "Anonymous",
52+
Params: []eventParameter{
53+
{Type: "uint256", Indexed: true},
54+
},
55+
}
56+
57+
assertEventSignatureEqual(t, got, want)
58+
}
59+
60+
func TestParseEventSignatureNoParams(t *testing.T) {
61+
got, err := parseEventSignature("event Ping();")
62+
if err != nil {
63+
t.Fatalf("unexpected error: %v", err)
64+
}
65+
66+
want := eventSignature{
67+
Name: "Ping",
68+
Params: []eventParameter{},
69+
}
70+
71+
assertEventSignatureEqual(t, got, want)
72+
}
73+
74+
func TestParseEventSignatureTrimsWhitespace(t *testing.T) {
75+
got, err := parseEventSignature(" event EventWithSpaces ( address a , uint256 b ) ; ")
76+
if err != nil {
77+
t.Fatalf("unexpected error: %v", err)
78+
}
79+
80+
want := eventSignature{
81+
Name: "EventWithSpaces",
82+
Params: []eventParameter{
83+
{Type: "address", Name: "a"},
84+
{Type: "uint256", Name: "b"},
85+
},
86+
}
87+
88+
assertEventSignatureEqual(t, got, want)
89+
}
90+
91+
func TestParseEventSignatureArrayTypes(t *testing.T) {
92+
got, err := parseEventSignature("event WithArrays(address[] indexed owners, uint256[3] balances, bytes32[][] data);")
93+
if err != nil {
94+
t.Fatalf("unexpected error: %v", err)
95+
}
96+
97+
want := eventSignature{
98+
Name: "WithArrays",
99+
Params: []eventParameter{
100+
{Type: "address[]", Name: "owners", Indexed: true},
101+
{Type: "uint256[3]", Name: "balances"},
102+
{Type: "bytes32[][]", Name: "data"},
103+
},
104+
}
105+
106+
assertEventSignatureEqual(t, got, want)
107+
}
108+
109+
func TestParseEventSignaturePayableAddress(t *testing.T) {
110+
got, err := parseEventSignature("event WithPayable(address payable indexed recipient, address payable sender);")
111+
if err != nil {
112+
t.Fatalf("unexpected error: %v", err)
113+
}
114+
115+
want := eventSignature{
116+
Name: "WithPayable",
117+
Params: []eventParameter{
118+
{Type: "address payable", Name: "recipient", Indexed: true},
119+
{Type: "address payable", Name: "sender"},
120+
},
121+
}
122+
123+
assertEventSignatureEqual(t, got, want)
124+
}
125+
126+
func TestParseEventSignatureMissingParentheses(t *testing.T) {
127+
assertEventSignatureError(t, "event BrokenEvent;", "invalid event signature")
128+
}
129+
130+
func TestParseEventSignatureUnexpectedTokens(t *testing.T) {
131+
assertEventSignatureError(t, "event Bad(uint256 value extra);", "unexpected tokens")
132+
}
133+
134+
func TestParseEventSignatureUnbalancedTuple(t *testing.T) {
135+
assertEventSignatureError(t, "event Bad(tuple(uint256,string) value;", "unbalanced parentheses")
136+
}
137+
138+
func assertEventSignatureEqual(t *testing.T, got, want eventSignature) {
139+
if !reflect.DeepEqual(got, want) {
140+
t.Fatalf("parseEventSignature() = %#v, want %#v", got, want)
141+
}
142+
}
143+
144+
func assertEventSignatureError(t *testing.T, signature, substr string) {
145+
_, err := parseEventSignature(signature)
146+
if err == nil {
147+
t.Fatalf("expected error containing %q, got nil", substr)
148+
}
149+
if !strings.Contains(err.Error(), substr) {
150+
t.Fatalf("expected error containing %q, got %v", substr, err)
151+
}
152+
}
153+
154+
func TestParseEventSignatureNoPrefix(t *testing.T) {
155+
got, err := parseEventSignature("Transfer(address indexed from, address indexed to, uint256 value);")
156+
if err != nil {
157+
t.Fatalf("unexpected error: %v", err)
158+
}
159+
160+
want := eventSignature{
161+
Name: "Transfer",
162+
Params: []eventParameter{
163+
{Type: "address", Name: "from", Indexed: true},
164+
{Type: "address", Name: "to", Indexed: true},
165+
{Type: "uint256", Name: "value"},
166+
},
167+
}
168+
169+
assertEventSignatureEqual(t, got, want)
170+
}
171+
172+
func TestParseEventSignatureNoSemicolon(t *testing.T) {
173+
got, err := parseEventSignature("event Transfer(address indexed from, address indexed to, uint256 value)")
174+
if err != nil {
175+
t.Fatalf("unexpected error: %v", err)
176+
}
177+
178+
want := eventSignature{
179+
Name: "Transfer",
180+
Params: []eventParameter{
181+
{Type: "address", Name: "from", Indexed: true},
182+
{Type: "address", Name: "to", Indexed: true},
183+
{Type: "uint256", Name: "value"},
184+
},
185+
}
186+
187+
assertEventSignatureEqual(t, got, want)
188+
}
189+
190+
func TestParseEventSignatureNoPrefixOrSemicolon(t *testing.T) {
191+
got, err := parseEventSignature("Transfer(address indexed from, address indexed to, uint256 value)")
192+
if err != nil {
193+
t.Fatalf("unexpected error: %v", err)
194+
}
195+
196+
want := eventSignature{
197+
Name: "Transfer",
198+
Params: []eventParameter{
199+
{Type: "address", Name: "from", Indexed: true},
200+
{Type: "address", Name: "to", Indexed: true},
201+
{Type: "uint256", Name: "value"},
202+
},
203+
}
204+
205+
assertEventSignatureEqual(t, got, want)
206+
}
207+
208+
func TestParseEventSignatureBareEventKeyword(t *testing.T) {
209+
assertEventSignatureError(t, "event", "missing identifier and parameters")
210+
}
211+
212+
func TestParseEventSignatureNestedTupleArray(t *testing.T) {
213+
sig := "\treplaceDeposit((bytes4,bytes2,bytes,bytes,bytes,bytes4)[],(bytes,uint256,uint256),uint256,bytes32)"
214+
got, err := parseEventSignature(sig)
215+
if err != nil {
216+
t.Fatalf("unexpected error: %v", err)
217+
}
218+
219+
want := eventSignature{
220+
Name: "replaceDeposit",
221+
Params: []eventParameter{
222+
{Type: "(bytes4,bytes2,bytes,bytes,bytes,bytes4)[]"},
223+
{Type: "(bytes,uint256,uint256)"},
224+
{Type: "uint256"},
225+
{Type: "bytes32"},
226+
},
227+
}
228+
229+
assertEventSignatureEqual(t, got, want)
230+
}

0 commit comments

Comments
 (0)