Skip to content

Commit 3be8068

Browse files
authored
Overall improvements (#214)
1 parent fe21f3c commit 3be8068

24 files changed

+625
-80
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ go.work
2727
solgo
2828
playground/*
2929
bin/*
30+
.idea

ast/builder.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func (b *ASTBuilder) ToJSON() ([]byte, error) {
9494
return b.InterfaceToJSON(b.tree.GetRoot())
9595
}
9696

97-
// ToPrettyJSON converts the provided data to a JSON byte array.
97+
// InterfaceToJSON converts the provided data to a JSON byte array.
9898
func (b *ASTBuilder) InterfaceToJSON(data interface{}) ([]byte, error) {
9999
return json.Marshal(data)
100100
}
@@ -129,6 +129,10 @@ func (b *ASTBuilder) ImportFromJSON(ctx context.Context, jsonBytes []byte) (*Roo
129129
return toReturn, nil
130130
}
131131

132+
func (b *ASTBuilder) GetImports() []Node[NodeType] {
133+
return b.currentImports
134+
}
135+
132136
// GarbageCollect cleans up the ASTBuilder after resolving references.
133137
func (b *ASTBuilder) GarbageCollect() {
134138
b.currentEnums = nil

ast/contract.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ func (c *Contract) Parse(unitCtx *parser.SourceUnitContext, ctx *parser.Contract
390390
)
391391

392392
contractId := c.GetNextID()
393+
393394
contractNode := &Contract{
394395
Id: contractId,
395396
Name: ctx.Identifier().GetText(),

ast/reference.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ func (r *Resolver) resolveEntrySourceUnit() {
294294
for _, entry := range node.GetExportedSymbols() {
295295
if len(r.sources.EntrySourceUnitName) > 0 &&
296296
r.sources.EntrySourceUnitName == entry.GetName() {
297+
297298
r.tree.astRoot.SetEntrySourceUnit(entry.GetId())
298299
return
299300
}

ast/source_unit.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type SourceUnit[T NodeType] struct {
2424
AbsolutePath string `json:"absolutePath"` // AbsolutePath is the absolute path of the source unit.
2525
Name string `json:"name"` // Name is the name of the source unit. This is going to be one of the following: contract, interface or library name. It's here for convenience.
2626
NodeType ast_pb.NodeType `json:"nodeType"` // NodeType is the type of the AST node.
27+
Kind ast_pb.NodeType `json:"kind"` // Kind is the type of the AST node (contract, library, interface).
2728
Nodes []Node[NodeType] `json:"nodes"` // Nodes is the list of AST nodes.
2829
Src SrcNode `json:"src"` // Src is the source code location.
2930
}
@@ -107,6 +108,11 @@ func (s *SourceUnit[T]) GetType() ast_pb.NodeType {
107108
return s.NodeType
108109
}
109110

111+
// GetKind returns the type of the source unit.
112+
func (s *SourceUnit[T]) GetKind() ast_pb.NodeType {
113+
return s.Kind
114+
}
115+
110116
// GetSrc returns the source code location of the source unit.
111117
func (s *SourceUnit[T]) GetSrc() SrcNode {
112118
return s.Src
@@ -302,6 +308,7 @@ func (b *ASTBuilder) EnterSourceUnit(ctx *parser.SourceUnitContext) {
302308
if interfaceCtx, ok := child.(*parser.InterfaceDefinitionContext); ok {
303309
license := getLicenseFromSources(b.sources, b.comments, interfaceCtx.Identifier().GetText())
304310
sourceUnit := NewSourceUnit[Node[ast_pb.SourceUnit]](b, interfaceCtx.Identifier().GetText(), license)
311+
sourceUnit.Kind = ast_pb.NodeType_KIND_INTERFACE
305312
interfaceNode := NewInterfaceDefinition(b)
306313
interfaceNode.Parse(ctx, interfaceCtx, rootNode, sourceUnit)
307314
b.sourceUnits = append(b.sourceUnits, sourceUnit)
@@ -310,6 +317,7 @@ func (b *ASTBuilder) EnterSourceUnit(ctx *parser.SourceUnitContext) {
310317
if libraryCtx, ok := child.(*parser.LibraryDefinitionContext); ok {
311318
license := getLicenseFromSources(b.sources, b.comments, libraryCtx.Identifier().GetText())
312319
sourceUnit := NewSourceUnit[Node[ast_pb.SourceUnit]](b, libraryCtx.Identifier().GetText(), license)
320+
sourceUnit.Kind = ast_pb.NodeType_KIND_LIBRARY
313321
libraryNode := NewLibraryDefinition(b)
314322
libraryNode.Parse(ctx, libraryCtx, rootNode, sourceUnit)
315323
b.sourceUnits = append(b.sourceUnits, sourceUnit)
@@ -318,11 +326,23 @@ func (b *ASTBuilder) EnterSourceUnit(ctx *parser.SourceUnitContext) {
318326
if contractCtx, ok := child.(*parser.ContractDefinitionContext); ok {
319327
license := getLicenseFromSources(b.sources, b.comments, contractCtx.Identifier().GetText())
320328
sourceUnit := NewSourceUnit[Node[ast_pb.SourceUnit]](b, contractCtx.Identifier().GetText(), license)
329+
sourceUnit.Kind = ast_pb.NodeType_KIND_CONTRACT
321330
contractNode := NewContractDefinition(b)
322331
contractNode.Parse(ctx, contractCtx, rootNode, sourceUnit)
323332
b.sourceUnits = append(b.sourceUnits, sourceUnit)
324333
}
325334
}
335+
336+
// Idea here is to basically set the source unit entry name as soon as we have parsed all of the classes.
337+
// Now this won't be possible always but nevertheless. (In rest of the cases, resolver will take care of it)
338+
if b.sources.EntrySourceUnitName != "" {
339+
for _, sourceUnit := range b.sourceUnits {
340+
if b.sources.EntrySourceUnitName == sourceUnit.GetName() {
341+
rootNode.SetEntrySourceUnit(sourceUnit.GetId())
342+
return
343+
}
344+
}
345+
}
326346
}
327347

328348
// ExitSourceUnit is called when the ASTBuilder exits a source unit context.

ast/src.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import (
66

77
// SrcNode represents a node in the source code.
88
type SrcNode struct {
9-
Line int64 `json:"line"` // Line number of the source node in the source code.
10-
Column int64 `json:"column"` // Column number of the source node in the source code.
11-
Start int64 `json:"start"` // Start position of the source node in the source code.
12-
End int64 `json:"end"` // End position of the source node in the source code.
13-
Length int64 `json:"length"` // Length of the source node in the source code.
14-
ParentIndex int64 `json:"parent_index,omitempty"` // Index of the parent node in the source code.
9+
Line int64 `json:"line"` // Line number of the source node in the source code.
10+
Column int64 `json:"column"` // Column number of the source node in the source code.
11+
Start int64 `json:"start"` // Start position of the source node in the source code.
12+
End int64 `json:"end"` // End position of the source node in the source code.
13+
Length int64 `json:"length"` // Length of the source node in the source code.
14+
ParentIndex int64 `json:"parentIndex,omitempty"` // Index of the parent node in the source code.
1515
}
1616

1717
// GetLine returns the line number of the source node in the source code.

ast/state_variable.go

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package ast
22

33
import (
4-
"strings"
5-
64
v3 "github.com/cncf/xds/go/xds/type/v3"
5+
"github.com/goccy/go-json"
76
ast_pb "github.com/unpackdev/protos/dist/go/ast"
87
"github.com/unpackdev/solgo/parser"
8+
"strings"
99
)
1010

1111
// StateVariableDeclaration represents a state variable declaration in the Solidity abstract syntax tree (AST).
@@ -145,6 +145,102 @@ func (v *StateVariableDeclaration) GetInitialValue() Node[NodeType] {
145145
return v.InitialValue
146146
}
147147

148+
// UnmarshalJSON customizes the JSON unmarshaling for StateVariableDeclaration.
149+
func (v *StateVariableDeclaration) UnmarshalJSON(data []byte) error {
150+
var tempMap map[string]json.RawMessage
151+
if err := json.Unmarshal(data, &tempMap); err != nil {
152+
return err
153+
}
154+
155+
if id, ok := tempMap["id"]; ok {
156+
if err := json.Unmarshal(id, &v.Id); err != nil {
157+
return err
158+
}
159+
}
160+
161+
if name, ok := tempMap["name"]; ok {
162+
if err := json.Unmarshal(name, &v.Name); err != nil {
163+
return err
164+
}
165+
}
166+
167+
if isConstant, ok := tempMap["isConstant"]; ok {
168+
if err := json.Unmarshal(isConstant, &v.Constant); err != nil {
169+
return err
170+
}
171+
}
172+
173+
if isStateVariable, ok := tempMap["isStateVariable"]; ok {
174+
if err := json.Unmarshal(isStateVariable, &v.StateVariable); err != nil {
175+
return err
176+
}
177+
}
178+
179+
if nodeType, ok := tempMap["nodeType"]; ok {
180+
if err := json.Unmarshal(nodeType, &v.NodeType); err != nil {
181+
return err
182+
}
183+
}
184+
185+
if visibility, ok := tempMap["visibility"]; ok {
186+
if err := json.Unmarshal(visibility, &v.Visibility); err != nil {
187+
return err
188+
}
189+
}
190+
191+
if storageLocation, ok := tempMap["storageLocation"]; ok {
192+
if err := json.Unmarshal(storageLocation, &v.StorageLocation); err != nil {
193+
return err
194+
}
195+
}
196+
197+
if mutability, ok := tempMap["mutability"]; ok {
198+
if err := json.Unmarshal(mutability, &v.StateMutability); err != nil {
199+
return err
200+
}
201+
}
202+
203+
if src, ok := tempMap["src"]; ok {
204+
if err := json.Unmarshal(src, &v.Src); err != nil {
205+
return err
206+
}
207+
}
208+
209+
if scope, ok := tempMap["scope"]; ok {
210+
if err := json.Unmarshal(scope, &v.Scope); err != nil {
211+
return err
212+
}
213+
}
214+
215+
if expression, ok := tempMap["initialValue"]; ok {
216+
if err := json.Unmarshal(expression, &v.InitialValue); err != nil {
217+
var tempNodeMap map[string]json.RawMessage
218+
if err := json.Unmarshal(expression, &tempNodeMap); err != nil {
219+
return err
220+
}
221+
222+
var tempNodeType ast_pb.NodeType
223+
if err := json.Unmarshal(tempNodeMap["nodeType"], &tempNodeType); err != nil {
224+
return err
225+
}
226+
227+
node, err := unmarshalNode(expression, tempNodeType)
228+
if err != nil {
229+
return err
230+
}
231+
v.InitialValue = node
232+
}
233+
}
234+
235+
if typeDescription, ok := tempMap["typeDescription"]; ok {
236+
if err := json.Unmarshal(typeDescription, &v.TypeDescription); err != nil {
237+
return err
238+
}
239+
}
240+
241+
return nil
242+
}
243+
148244
// ToProto returns the protobuf representation of the state variable declaration.
149245
func (v *StateVariableDeclaration) ToProto() NodeType {
150246
proto := ast_pb.StateVariable{

bindings/otterscan.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bindings
33
import (
44
"context"
55
"fmt"
6+
"github.com/pkg/errors"
67

78
"github.com/ethereum/go-ethereum/common"
89
"github.com/unpackdev/solgo/utils"
@@ -19,9 +20,9 @@ type CreatorInformation struct {
1920
// GetContractCreator queries the Ethereum blockchain to find the creator of a specified smart contract. This method
2021
// utilizes the Ethereum JSON-RPC API to request creator information, which includes both the creator's address and
2122
// the transaction hash of the contract's creation. It's a valuable tool for auditing and tracking the origins of
22-
// contracts on the network. WORKS ONLY WITH ERIGON NODE - OR NODES THAT SUPPORT OTTERSCAN!
23+
// contracts on the network. WORKS ONLY WITH ERIGON NODE OR QUICKNODE PROVIDER - OR NODES THAT SUPPORT OTTERSCAN!
2324
func (m *Manager) GetContractCreator(ctx context.Context, network utils.Network, contract common.Address) (*CreatorInformation, error) {
24-
client := m.clientPool.GetClientByGroup(string(network))
25+
client := m.clientPool.GetClientByGroup(network.String())
2526
if client == nil {
2627
return nil, fmt.Errorf("client not found for network %s", network)
2728
}
@@ -30,7 +31,26 @@ func (m *Manager) GetContractCreator(ctx context.Context, network utils.Network,
3031
var result *CreatorInformation
3132

3233
if err := rpcClient.CallContext(ctx, &result, "ots_getContractCreator", contract.Hex()); err != nil {
33-
return nil, fmt.Errorf("failed to fetch otterscan creator information: %v", err)
34+
return nil, errors.Wrap(err, "failed to fetch otterscan creator information")
35+
}
36+
37+
return result, nil
38+
}
39+
40+
// GetTransactionBySenderAndNonce retrieves a transaction hash based on a specific sender's address and nonce.
41+
// This function also utilizes the Ethereum JSON-RPC API and requires a node that supports specific transaction lookup
42+
// by sender and nonce, which is particularly useful for tracking transaction sequences and debugging transaction flows.
43+
func (m *Manager) GetTransactionBySenderAndNonce(ctx context.Context, network utils.Network, sender common.Address, nonce int64) (*common.Hash, error) {
44+
client := m.clientPool.GetClientByGroup(network.String())
45+
if client == nil {
46+
return nil, fmt.Errorf("client not found for network %s", network)
47+
}
48+
49+
rpcClient := client.GetRpcClient()
50+
var result *common.Hash
51+
52+
if err := rpcClient.CallContext(ctx, &result, "ots_getTransactionBySenderAndNonce", sender.Hex(), nonce); err != nil {
53+
return nil, errors.Wrap(err, "failed to fetch otterscan get transaction by sender and nonce information")
3454
}
3555

3656
return result, nil

bindings/trace.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package bindings
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/pkg/errors"
7+
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/unpackdev/solgo/utils"
10+
)
11+
12+
func (m *Manager) TraceCallMany(ctx context.Context, network utils.Network, sender common.Address, nonce int64) (*common.Hash, error) {
13+
client := m.clientPool.GetClientByGroup(network.String())
14+
if client == nil {
15+
return nil, fmt.Errorf("client not found for network %s", network)
16+
}
17+
18+
rpcClient := client.GetRpcClient()
19+
var result *common.Hash
20+
21+
if err := rpcClient.CallContext(ctx, &result, "trace_callMany", sender.Hex(), nonce); err != nil {
22+
return nil, errors.Wrap(err, "failed to execute trace_callMany")
23+
}
24+
25+
return result, nil
26+
}

contracts/descriptor.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ type Descriptor struct {
4040

4141
// SourcesRaw is the raw sources from Etherscan|BscScan|etc. Should not be used anywhere except in
4242
// the contract discovery process.
43-
SourcesRaw *etherscan.Contract `json:"-"`
44-
Sources *solgo.Sources `json:"sources,omitempty"`
45-
SourceProvider string `json:"source_provider,omitempty"`
43+
SourcesRaw *etherscan.Contract `json:"-"`
44+
Sources *solgo.Sources `json:"sources,omitempty"`
45+
SourcesUnsorted *solgo.Sources `json:"-"`
46+
SourceProvider string `json:"source_provider,omitempty"`
4647

4748
// Source detection related fields.
4849
Detector *detector.Detector `json:"-"`
@@ -144,6 +145,11 @@ func (d *Descriptor) GetSources() *solgo.Sources {
144145
return d.Sources
145146
}
146147

148+
// GetUnsortedSources returns the parsed sources of the contract, providing a structured view of the contract's code.
149+
func (d *Descriptor) GetUnsortedSources() *solgo.Sources {
150+
return d.SourcesUnsorted
151+
}
152+
147153
// GetSourcesRaw returns the raw contract source as obtained from external providers like Etherscan.
148154
func (d *Descriptor) GetSourcesRaw() *etherscan.Contract {
149155
return d.SourcesRaw

0 commit comments

Comments
 (0)