Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolution of ABI parsing for nested structs #9

Merged
merged 1 commit into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions abi_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ func normalizeTypeName(typeName string) string {
}
}

// normalizeStructTypeName normalizes the type name of a struct in Solidity to its canonical form.
// For example, "structName[]" is normalized to "tuple[]", and "structName" is normalized to "tuple".
func normalizeStructTypeName(definedStructs map[string]MethodIO, typeName string) string {
switch {
case strings.HasSuffix(typeName, "[]") && isStructType(definedStructs, strings.TrimSuffix(typeName, "[]")):
// Handle array of structs
return "tuple[]"
default:
return "tuple"
}
}

// isMappingType checks if the given type name represents a mapping type in Solidity.
// It returns true if the type name contains the string "mapping", and false otherwise.
func isMappingType(name string) bool {
Expand All @@ -31,6 +43,7 @@ func isMappingType(name string) bool {
// definedStructs is a map from struct names to MethodIO objects representing the struct located in the AbiParser.
// Returns true if the type name corresponds to a defined struct, false otherwise.
func isStructType(definedStructs map[string]MethodIO, typeName string) bool {
typeName = strings.TrimRight(typeName, "[]")
_, exists := definedStructs[typeName]
return exists
}
Expand Down
2 changes: 1 addition & 1 deletion abi_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (l *AbiListener) EnterStructDefinition(ctx *parser.StructDefinitionContext)
// ExitStructDefinition is called when the parser exits a struct definition.
// It resolves the components of the struct in the ABI.
func (l *AbiListener) ExitStructDefinition(ctx *parser.StructDefinitionContext) {
if err := l.parser.ResolveStructComponents(); err != nil {
if err := l.parser.ResolveStruct(ctx); err != nil {
zap.L().Error(
"failed to resolve struct components",
zap.Error(err),
Expand Down
2 changes: 1 addition & 1 deletion abi_listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestAbiListener(t *testing.T) {
{
name: "Structs",
contract: ReadContractFileForTest(t, "Structs").Content,
expected: `[{"inputs":[{"internalType":"struct MyStructs.ClasicStruct","name":"ClasicStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"}]},{"internalType":"struct MyStructs.NestedStruct","name":"NestedStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"},{"internalType":"struct MyStructs.ClasicStruct","name":"myStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"}]}]},{"internalType":"uint256","name":"Integer","type":"uint256"}],"outputs":[{"internalType":"struct MyStructs.NestedStruct","name":"NestedStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"},{"internalType":"struct MyStructs.ClasicStruct","name":"myStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"}]}]}],"name":"nestedStructExample","type":"function","stateMutability":"pure"}]`,
expected: `[{"inputs":[{"internalType":"struct MyStructs.ClasicStruct","name":"ClasicStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"}]},{"internalType":"struct MyStructs.NestedStruct","name":"NestedStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"},{"internalType":"struct MyStructs.ClasicStruct","name":"myStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"}]}]},{"internalType":"uint256","name":"Integer","type":"uint256"}],"outputs":[{"internalType":"struct MyStructs.NestedStruct","name":"NestedStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"},{"internalType":"struct MyStructs.ClasicStruct","name":"myStruct","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"}]}]}],"name":"nestedStructExample","type":"function","stateMutability":"pure"},{"inputs":[{"internalType":"struct MyStructs.StructWithArray","name":"StructWithArray","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256[]","name":"array","type":"uint256[]"}]}],"outputs":[],"name":"structWithArrayExample","type":"function","stateMutability":"pure"},{"inputs":[{"internalType":"struct MyStructs.StructWithNestedArray","name":"StructWithNestedArray","type":"tuple","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"struct MyStructs.ClasicStruct[]","name":"structArray","type":"tuple[]","components":[{"internalType":"uint256","name":"one","type":"uint256"},{"internalType":"uint256","name":"two","type":"uint256"}]}]}],"outputs":[],"name":"structWithNestedArrayExample","type":"function","stateMutability":"pure"}]`,
},
{
name: "Enums",
Expand Down
11 changes: 6 additions & 5 deletions abi_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ func (p *AbiParser) InjectFunction(ctx *parser.FunctionDefinitionContext) error
}()

if isStructType(p.definedStructs, paramCtx.TypeName().GetText()) {
// Checking if the parameter is a struct...
nestedComponent, err := p.getStructComponents(paramCtx.TypeName().GetText())
if err != nil {
zap.L().Error(
Expand Down Expand Up @@ -317,16 +316,17 @@ func (p *AbiParser) AppendStruct(ctx *parser.StructDefinitionContext) error {
return nil
}

// ResolveStructComponents iterates over the defined structs in the AbiParser and resolves their components.
// ResolveStruct iterates over the defined structs in the AbiParser and resolves their components.
// If a component is of a struct type, it retrieves the components of the nested struct and updates the component's type to "tuple".
// The component's InternalType is also updated to reflect the struct's name and the contract it belongs to.
// If a struct component cannot be resolved, it logs a debug message and returns an error.
// This function is useful for resolving nested structs and should be called after all structs have been defined.
func (p *AbiParser) ResolveStructComponents() error {
func (p *AbiParser) ResolveStruct(ctx *parser.StructDefinitionContext) error {
for structName, structIO := range p.definedStructs {
for i, component := range structIO.Components {
if isStructType(p.definedStructs, component.Type) {
nestedComponent, err := p.getStructComponents(component.Type)
basicStructType := strings.TrimRight(component.Type, "[]")
nestedComponent, err := p.getStructComponents(basicStructType)
if err != nil {
// Problematic is that if there are multiple passes to resolve structs,
// we will get multiple errors for the same struct while at the same time at the last pass
Expand All @@ -343,8 +343,9 @@ func (p *AbiParser) ResolveStructComponents() error {
)
return err
}

structIO.Components[i].Components = nestedComponent.Components
structIO.Components[i].Type = "tuple"
structIO.Components[i].Type = normalizeStructTypeName(p.definedStructs, component.Type)
structIO.Components[i].InternalType = fmt.Sprintf("struct %s.%s", p.contractName, component.Type)
}
}
Expand Down
1 change: 0 additions & 1 deletion data/tests/Mappings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ contract MyMappings {
mapping(address=>uint) public simpleMapping;
mapping(address=>mapping(address=>uint)) public doubleMapping;
mapping(address=>mapping(address=>mapping(address=>uint))) public tripleMapping;

}
26 changes: 23 additions & 3 deletions data/tests/Structs.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// This contract will not work, it's not designed for it to work but to test the parser in
// many different ways, types, modifiers, etc.

contract MyStructs {
struct NestedStruct {
uint256 one;
Expand All @@ -16,6 +13,29 @@ contract MyStructs {
uint256 two;
}

struct StructWithArray {
uint256 one;
uint256[] array;
}

struct StructWithMapping {
uint256 one;
mapping(address => uint256) map;
}

struct StructWithNestedArray {
uint256 one;
ClasicStruct[] structArray;
}

struct StructWithNestedMapping {
uint256 one;
mapping(address => ClasicStruct) structMap;
}

function nestedStructExample(ClasicStruct memory structOne, NestedStruct memory structTwo, uint Integer) public pure returns (NestedStruct memory structReturn) {}

function structWithArrayExample(StructWithArray memory structOne) public pure {}

function structWithNestedArrayExample(StructWithNestedArray memory structOne) public pure {}
}
Empty file added data/tests/dummy.json
Empty file.