Skip to content
Open
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
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,67 @@ Then install python requirements. Python >= 3.5 is required.

```pip install -r requirements.txt```

### Solidity Compatibility

ProtoSolGenerator includes comprehensive compatibility features for Solidity development:

#### Reserved Keyword Handling

The generator automatically mangles protobuf field names that conflict with Solidity reserved keywords:

- **Automatic detection**: Comprehensive list of Solidity reserved keywords including types (uint256, bytes32, etc.), control flow (if, while, etc.), and language features (contract, library, etc.)
- **Safe mangling**: Conflicting field names are automatically suffixed with `_field` (e.g., `uint32` → `uint32_field`)
- **No manual intervention**: Field name conflicts are resolved transparently during code generation

#### Enhanced Parameter Processing

The plugin supports both value-based and boolean flag parameters:

```bash
# Boolean flags (automatically detected)
--sol_opt=gen_runtime
--sol_opt=use_builtin_enum

# Value-based parameters
--sol_opt=gen_runtime=MyRuntime.sol
--sol_opt=solc_version=0.8.19
```

This enables more flexible plugin configuration and better integration with build tools like buf.

### Circular Dependency Resolution

ProtoSolGenerator includes advanced handling for circular dependencies commonly found in validation schemas:

#### Automatic Detection

The generator automatically detects circular references in message structures, particularly in buf/validate types:

- **FieldRules** ↔ **MapRules**: Field rules can reference map validation rules
- **FieldRules** ↔ **RepeatedRules**: Field rules can reference repeated field validation rules
- **MapRules** → **FieldRules**: Map rules reference field rules for key/value validation

#### Bytes Field Solution

Circular dependencies are resolved by using `bytes` fields with encode/decode helpers:

```solidity
struct BufValidateFieldRules {
bytes map_field; // Circular reference: use encode/decode helpers
bytes repeated_field; // Circular reference: use encode/decode helpers
// ... other fields
}
```

The circular fields are marked with descriptive comments and can be serialized/deserialized using the standard protobuf encoding functions, maintaining full compatibility while avoiding Solidity compilation errors.

#### No Manual Intervention

Circular dependency resolution is automatic and transparent:
- Detected at generation time based on message and field type analysis
- Fields are automatically converted to `bytes` representation where needed
- Original protobuf semantics are preserved through encode/decode operations

### Data Definition

Users can use the custom type definition to define their data structure. Below is an example:
Expand Down
50 changes: 30 additions & 20 deletions protobuf-solidity/src/protoc/plugin/gen_decoder.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gen_util as util
from gen_util import mangle_solidity_field_name
import gen_decoder_constants as decoder_constants
from google.protobuf.descriptor import Descriptor, FieldDescriptor

Expand All @@ -19,32 +20,32 @@ def gen_inner_field_decoder(field: FieldDescriptor, first_pass: bool, index: int
return (decoder_constants.INNER_REPEATED_SCALAR_NUMERIC_FIELD_DECODER_FIRST_PASS).format(
control = ("else " if index > 0 else ""),
id = field.number,
field = field.name
field = mangle_solidity_field_name(field.name)
)
elif first_pass:
return (decoder_constants.INNER_REPEATED_NON_SCALAR_NUMERIC_FIELD_DECODER_FIRST_PASS).format(
control = ("else " if index > 0 else ""),
id = field.number,
field = field.name
field = mangle_solidity_field_name(field.name)
)
elif scalar_numeric:
return (decoder_constants.INNER_REPEATED_SCALAR_NUMERIC_FIELD_DECODER_SECOND_PASS).format(
control = ("else " if index > 0 else ""),
id = field.number,
field = field.name
field = mangle_solidity_field_name(field.name)
)
else:
return (decoder_constants.INNER_REPEATED_NON_SCALAR_NUMERIC_FIELD_DECODER_SECOND_PASS).format(
control = ("else " if index > 0 else ""),
id = field.number,
field = field.name
field = mangle_solidity_field_name(field.name)
)
else:
if first_pass:
return (decoder_constants.INNER_FIELD_DECODER).format(
control = ("else " if index > 0 else ""),
id = field.number,
field = field.name
field = mangle_solidity_field_name(field.name)
)
else:
return ""
Expand All @@ -63,15 +64,15 @@ def gen_inner_array_allocator(f: FieldDescriptor, is_repeated: bool) -> str:
return ""
return (decoder_constants.INNER_ARRAY_ALLOCATOR).format(
t = util.gen_global_type_from_field(f),
field = f.name,
field = mangle_solidity_field_name(f.name),
i = f.number
)

def gen_inner_map_size(f: FieldDescriptor) -> str:
if not util.is_map_type(f):
return ""
return (decoder_constants.INNER_MAP_SIZE).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
i = f.number
)

Expand Down Expand Up @@ -117,7 +118,7 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str:
assert decode_type[0] != "."
if not is_repeated:
return (decoder_constants.ENUM_FIELD_READER).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = decode_type,
t = util.gen_internal_struct_name(msg),
Expand All @@ -126,7 +127,7 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str:
)
else:
unpacked_reader = (decoder_constants.UNPACKED_REPEATED_ENUM_FIELD_READER).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = decode_type,
t = util.gen_internal_struct_name(msg),
Expand All @@ -136,7 +137,7 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str:
library_name = library_name
)
packed_reader = (decoder_constants.PACKED_REPEATED_ENUM_FIELD_READER).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = decode_type,
t = util.gen_internal_struct_name(msg),
Expand All @@ -145,14 +146,23 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str:
)
return unpacked_reader + packed_reader
if not is_repeated:
return (decoder_constants.FIELD_READER).format(
field = f.name,
decoder = util.gen_decoder_name(f),
decode_type = util.gen_global_type_decl_from_field(f),
t = util.gen_internal_struct_name(msg),
)
# Handle circular dependencies by using bytes decoder instead of struct decoder
if util.should_use_bytes_for_field(msg, f):
return (decoder_constants.FIELD_READER).format(
field = mangle_solidity_field_name(f.name),
decoder = "ProtoBufRuntime._decode_bytes",
decode_type = "bytes memory",
t = util.gen_internal_struct_name(msg),
)
else:
return (decoder_constants.FIELD_READER).format(
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = util.gen_global_type_decl_from_field(f),
t = util.gen_internal_struct_name(msg),
)
unpacked_reader = (decoder_constants.UNPACKED_REPEATED_FIELD_READER).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = util.gen_global_type_decl_from_field(f),
t = util.gen_internal_struct_name(msg),
Expand All @@ -162,21 +172,21 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str:
packed_reader = '' # this remains empty if wire type is length-delimited
if util.gen_wire_type(f) == 'Fixed32':
packed_reader = (decoder_constants.PACKED_REPEATED_FIXED32_FIELD_READER).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = util.gen_global_type_decl_from_field(f),
t = util.gen_internal_struct_name(msg)
)
elif util.gen_wire_type(f) == 'Fixed64':
packed_reader = (decoder_constants.PACKED_REPEATED_FIXED64_FIELD_READER).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = util.gen_global_type_decl_from_field(f),
t = util.gen_internal_struct_name(msg)
)
elif util.gen_wire_type(f) == 'Varint':
packed_reader = (decoder_constants.PACKED_REPEATED_VARINT_FIELD_READER).format(
field = f.name,
field = mangle_solidity_field_name(f.name),
decoder = util.gen_decoder_name(f),
decode_type = util.gen_global_type_decl_from_field(f),
t = util.gen_internal_struct_name(msg)
Expand Down
29 changes: 22 additions & 7 deletions protobuf-solidity/src/protoc/plugin/gen_encoder.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gen_util as util
from gen_util import mangle_solidity_field_name
import gen_encoder_constants as encoder_constants
from google.protobuf.descriptor import Descriptor, FieldDescriptor
from typing import List
Expand All @@ -19,7 +20,11 @@ def gen_inner_field_encoder(f: FieldDescriptor, msg: Descriptor) -> str:
"""Generates a code snippet that encodes a field (a part of _encode)"""
is_repeated = util.field_is_repeated(f)
is_packed = util.field_is_packed(f)
if is_repeated:

# Handle circular dependencies by using bytes encoder
if util.should_use_bytes_for_field(msg, f):
template = encoder_constants.INNER_FIELD_ENCODER_NOT_REPEATED
elif is_repeated:
if util.is_map_type(f):
template = encoder_constants.INNER_FIELD_ENCODER_REPEATED_MAP
elif f.type == FieldDescriptor.TYPE_ENUM and is_packed:
Expand All @@ -44,12 +49,17 @@ def gen_inner_field_encoder(f: FieldDescriptor, msg: Descriptor) -> str:
if is_repeated and is_packed:
size = gen_packed_repeated_array_size(f)
ecblk = util.EmptyCheckBlock(f)

# Use bytes encoder for circular dependencies
encoder_name = "ProtoBufRuntime._encode_bytes" if util.should_use_bytes_for_field(msg, f) else util.gen_encoder_name(f)
wire_type = "LengthDelim" if util.should_use_bytes_for_field(msg, f) else util.gen_wire_type(f)

return template.format(
block_begin=ecblk.begin(),
field = f.name,
field = mangle_solidity_field_name(f.name),
key = f.number,
wiretype = util.gen_wire_type(f),
encoder = util.gen_encoder_name(f),
wiretype = wire_type,
encoder = encoder_name,
enum_name = type_name.split(".")[-1],
library_name = library_name,
size = size,
Expand Down Expand Up @@ -81,7 +91,7 @@ def gen_packed_repeated_array_size(f: FieldDescriptor) -> str:
assert util.field_is_repeated(f)
assert util.field_is_packed(f)
wt = util.gen_wire_type(f)
field = f.name
field = mangle_solidity_field_name(f.name)
if wt == "Fixed32":
return "(4 * r.{field}.length)".format(field = field)
if wt == "Fixed64":
Expand All @@ -108,13 +118,18 @@ def gen_packed_repeated_array_size(f: FieldDescriptor) -> str:
Determine the estimated size given the field type
"""
def gen_field_scalar_size(f: FieldDescriptor, msg: Descriptor) -> str:
# Handle circular dependencies as bytes
if util.should_use_bytes_for_field(msg, f):
fname = mangle_solidity_field_name(f.name) + ("[i]" if util.field_is_repeated(f) else "")
return ("ProtoBufRuntime._sz_lendelim(r.{field}.length)").format(field = fname)

wt = util.gen_wire_type(f)
vt = util.field_pb_type(f)
if util.field_is_repeated(f) and util.field_is_packed(f):
return "ProtoBufRuntime._sz_lendelim({})".format(
gen_packed_repeated_array_size(f)
)
fname = f.name + ("[i]" if util.field_is_repeated(f) else "")
fname = mangle_solidity_field_name(f.name) + ("[i]" if util.field_is_repeated(f) else "")
if wt == "Varint":
if vt == "bool":
return "1"
Expand Down Expand Up @@ -169,7 +184,7 @@ def gen_field_estimator(f: FieldDescriptor, msg: Descriptor) -> str:
else:
template = encoder_constants.FIELD_ESTIMATOR_NOT_REPEATED
return template.format(
field = f.name,
field = mangle_solidity_field_name(f.name),
szKey = (1 if f.number < 16 else 2),
szItem = gen_field_scalar_size(f, msg)
)
Expand Down
Loading