diff --git a/README.md b/README.md index 0fda966..f379e8b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,141 @@ Then install python requirements. Python >= 3.5 is required. ```pip install -r requirements.txt``` +### Runtime Enhancements + +ProtoSolGenerator includes enhanced runtime support for additional protobuf types and Google well-known types: + +#### Float and Double Support + +The runtime includes native support for protobuf `float` (32-bit) and `double` (64-bit) types: + +- `_decode_float(uint256 p, bytes memory bs)` - Decodes protobuf float to bytes4 +- `_encode_float(bytes4 x, uint256 p, bytes memory bs)` - Encodes bytes4 to protobuf float +- `_decode_double(uint256 p, bytes memory bs)` - Decodes protobuf double to bytes8 +- `_encode_double(bytes8 x, uint256 p, bytes memory bs)` - Encodes bytes8 to protobuf double + +These functions enable proper handling of floating-point validation constraints from buf/validate and other protobuf schemas. + +#### Google Well-Known Types + +Runtime libraries are provided for common Google protobuf types: + +- **GoogleProtobufAny.sol** - Support for `google.protobuf.Any` messages +- **GoogleProtobufDuration.sol** - Support for `google.protobuf.Duration` messages +- **GoogleProtobufTimestamp.sol** - Support for `google.protobuf.Timestamp` messages +- **GoogleProtobufFieldDescriptorProto.sol** - Support for `google.protobuf.FieldDescriptorProto` messages + +These libraries are automatically included when generating contracts that use the corresponding Google protobuf types. + +### 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 + +### BSR Plugin Configuration + +ProtoSolGenerator can be configured for distribution via the Buf Schema Registry (BSR): + +#### Plugin Metadata + +The included `buf.plugin.yaml` defines the plugin configuration: + +```yaml +version: v1 +name: buf.build/datachainlab/protoc-gen-solidity +plugin_version: v1.0.0 +description: "Generate Solidity contracts from protobuf with validation support" +source_url: "https://github.com/datachainlab/solidity-protobuf" +deps: + - buf.build/protocolbuffers/protobuf + - buf.build/bufbuild/protovalidate +runtime: + python: + requirements: + - protobuf>=4.25.1 + - wrapt +spdx_license_id: Apache-2.0 +license_url: "https://github.com/datachainlab/solidity-protobuf/blob/master/LICENSE" +``` + +#### Local buf.gen.yaml Usage + +For local development with the plugin: + +```yaml +version: v2 +plugins: + - local: ["python", "protobuf-solidity/src/protoc/plugin/gen_sol.py"] + out: contracts/generated + opt: gen_runtime=true +``` + +#### Plugin Options + +The plugin supports various configuration options: + +- `gen_runtime=true`: Generate runtime libraries (ProtoBufRuntime.sol, etc.) +- `gen_runtime=CustomRuntime.sol`: Generate with custom runtime filename +- `solc_version=0.8.19`: Target specific Solidity compiler version +- `for_linking`: Enable library linking mode for larger projects +- `pb_libname=Custom`: Set custom library name prefix + ### Data Definition Users can use the custom type definition to define their data structure. Below is an example: diff --git a/buf.plugin.yaml b/buf.plugin.yaml new file mode 100644 index 0000000..d43254a --- /dev/null +++ b/buf.plugin.yaml @@ -0,0 +1,15 @@ +version: v1 +name: buf.build/cyberstorm/protoc-gen-solidity +plugin_version: v1.0.0-cyberstorm +description: "Generate Solidity contracts from protobuf with validation support and circular dependency fixes" +source_url: "https://github.com/cyberstorm-dev/solidity-protobuf" +deps: + - buf.build/protocolbuffers/protobuf + - buf.build/bufbuild/protovalidate +runtime: + python: + requirements: + - protobuf>=4.25.1 + - wrapt +spdx_license_id: Apache-2.0 +license_url: "https://github.com/datachainlab/solidity-protobuf/blob/master/LICENSE" \ No newline at end of file diff --git a/protobuf-solidity/README.md b/protobuf-solidity/README.md new file mode 100644 index 0000000..e33b428 --- /dev/null +++ b/protobuf-solidity/README.md @@ -0,0 +1,99 @@ +# protobuf-solidity + +A Protocol Buffers compiler plugin for generating Solidity code from `.proto` files. + +## Features + +### Message Generation +- Generates Solidity structs for Protocol Buffer messages +- Includes encoder/decoder functions for each message type +- Supports nested messages and repeated fields +- Handles all standard protobuf types (strings, integers, booleans, bytes) + +### Contract Interface Generation +- Generates Solidity interfaces from Protocol Buffer service definitions +- Maps proto RPC methods to Solidity function signatures +- Maintains type safety between proto definitions and Solidity contracts +- Enables contract-first development with proto as single source of truth + +## Usage + +### Installation + +Link the plugin for use with `protoc`: + +```bash +ln -s src/protoc/plugin/gen_sol.py protoc-gen-solidity +chmod +x protoc-gen-solidity +``` + +### Basic Usage + +Generate Solidity files from proto definitions: + +```bash +protoc --solidity_out=./generated *.proto +``` + +### Proto Service to Contract Interface + +Define a service in your `.proto` file: + +```protobuf +service ContractInterface { + rpc RegisterIdentity(RegisterIdentityRequest) returns (RegisterIdentityResponse); + rpc GetIdentities(GetIdentitiesRequest) returns (GetIdentitiesResponse); +} +``` + +The plugin generates a corresponding Solidity interface: + +```solidity +interface IContractInterface { + function registerIdentity(string memory domain, string memory identifier) + external returns (bool success, string memory error, string memory transaction_hash); + function getIdentities() + external returns (StandaloneAttestorStandaloneIdentity.Data[] memory identities); +} +``` + +### Contract Implementation + +Implement the generated interface in your contract: + +```solidity +contract MyContract is IContractInterface { + function registerIdentity(string memory domain, string memory identifier) + external override returns (bool success, string memory error, string memory transaction_hash) { + // Implementation + } +} +``` + +## Type Mapping + +| Proto Type | Solidity Type | +|------------|---------------| +| `string` | `string memory` | +| `int32` | `int32` | +| `int64` | `int64` | +| `uint32` | `uint32` | +| `uint64` | `uint64` | +| `bool` | `bool` | +| `bytes` | `bytes memory` | +| `repeated T` | `T[] memory` | +| Message type | `LibraryName.Data memory` | + +## Architecture + +- **Message Types**: Generated as libraries with `Data` structs and encode/decode functions +- **Service Interfaces**: Generated as Solidity interfaces with function signatures matching RPC methods +- **Type Safety**: Compile-time verification that contracts match proto definitions +- **Modularity**: Each proto file generates corresponding Solidity libraries and interfaces + +## Benefits + +- **Single Source of Truth**: Proto files define both backend contracts and frontend types +- **Type Safety**: Compile-time checking ensures implementation matches specification +- **Consistency**: Same data structures across different parts of the application +- **Maintainability**: Changes to proto files automatically propagate to generated code \ No newline at end of file diff --git a/protobuf-solidity/protoc-gen-solidity b/protobuf-solidity/protoc-gen-solidity new file mode 120000 index 0000000..d2c2e74 --- /dev/null +++ b/protobuf-solidity/protoc-gen-solidity @@ -0,0 +1 @@ +src/protoc/plugin/gen_sol.py \ No newline at end of file diff --git a/protobuf-solidity/src/protoc/plugin/gen_decoder.py b/protobuf-solidity/src/protoc/plugin/gen_decoder.py index 73cf3ac..8dfb53d 100644 --- a/protobuf-solidity/src/protoc/plugin/gen_decoder.py +++ b/protobuf-solidity/src/protoc/plugin/gen_decoder.py @@ -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 @@ -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 "" @@ -63,7 +64,7 @@ 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 ) @@ -71,7 +72,7 @@ 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 ) @@ -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), @@ -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), @@ -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), @@ -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), @@ -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) diff --git a/protobuf-solidity/src/protoc/plugin/gen_encoder.py b/protobuf-solidity/src/protoc/plugin/gen_encoder.py index 2e83386..4978e71 100644 --- a/protobuf-solidity/src/protoc/plugin/gen_encoder.py +++ b/protobuf-solidity/src/protoc/plugin/gen_encoder.py @@ -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 @@ -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: @@ -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, @@ -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": @@ -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" @@ -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) ) diff --git a/protobuf-solidity/src/protoc/plugin/gen_sol.py b/protobuf-solidity/src/protoc/plugin/gen_sol.py index b0dc932..f529ac0 100755 --- a/protobuf-solidity/src/protoc/plugin/gen_sol.py +++ b/protobuf-solidity/src/protoc/plugin/gen_sol.py @@ -13,16 +13,25 @@ from gen_decoder import gen_decoder_section from gen_encoder import gen_encoder_section import gen_util as util +from gen_util import mangle_solidity_field_name import gen_sol_constants as sol_constants import solidity_protobuf_extensions_pb2 as solpbext def gen_fields(msg: Descriptor) -> str: - return '\n'.join(map((lambda f: (" {type} {name};").format(type = util.gen_fieldtype(f), name = f.name)), msg.fields)) + def format_field(f): + field_name = mangle_solidity_field_name(f.name) + if util.should_use_bytes_for_field(msg, f): + # Use bytes for circular dependencies with a comment + return f" bytes {field_name}; // Circular reference: use encode/decode helpers" + else: + return f" {util.gen_fieldtype(f)} {field_name};" + + return '\n'.join(map(format_field, msg.fields)) def gen_map_fields_decl_for_field(f: FieldDescriptor) -> str: return (sol_constants.MAP_FIELD_DEFINITION).format( - name = f.name, + name = mangle_solidity_field_name(f.name), key_type = util.gen_global_type_name_from_field(f.message_type.fields[0]), container_type = util.gen_global_type_name_from_field(f) ) @@ -91,25 +100,30 @@ def gen_map_insert_on_store(f: FieldDescriptor, parent_msg: Descriptor) -> str: for nt in parent_msg.nested_types: if nt.GetOptions().map_entry: if f.message_type and f.message_type is nt: - return ('output._size_{name} = input._size_{name};\n').format(name = f.name) + return ('output._size_{name} = input._size_{name};\n').format(name = mangle_solidity_field_name(f.name)) return '' def gen_store_code_for_field(f: FieldDescriptor, msg: Descriptor) -> str: tmpl = "" - if util.field_is_message(f) and util.field_is_repeated(f): + # Handle circular dependencies as simple field assignment (bytes) + if util.should_use_bytes_for_field(msg, f): + return (sol_constants.STORE_OTHER).format( + field = mangle_solidity_field_name(f.name) + ) + elif util.field_is_message(f) and util.field_is_repeated(f): tmpl = sol_constants.STORE_REPEATED elif util.field_is_message(f): tmpl = sol_constants.STORE_MESSAGE else: return (sol_constants.STORE_OTHER).format( - field = f.name + field = mangle_solidity_field_name(f.name) ) libname = util.gen_struct_codec_lib_name_from_field(f) return tmpl.format( i = f.number, - field = f.name, + field = mangle_solidity_field_name(f.name), lib = libname, map_insert_code = gen_map_insert_on_store(f, msg) ) @@ -150,8 +164,8 @@ def gen_map_helper_codes_for_field(f: FieldDescriptor, nested_type: Descriptor) value_storage_type = "" return (sol_constants.MAP_HELPER_CODE).format( name = util.to_camel_case(f.name), - val_name = "self.{0}".format(f.name), - map_name = "self._size_{0}".format(f.name), + val_name = "self.{0}".format(mangle_solidity_field_name(f.name)), + map_name = "self._size_{0}".format(mangle_solidity_field_name(f.name)), key_type = key_type, value_type = value_type, field_type = field_type, @@ -164,7 +178,7 @@ def gen_array_helper_codes_for_field(f: FieldDescriptor) -> str: field_type = util.gen_global_type_name_from_field(f) return (sol_constants.ARRAY_HELPER_CODE).format( name = util.to_camel_case(f.name), - val_name = "self.{0}".format(f.name), + val_name = "self.{0}".format(mangle_solidity_field_name(f.name)), field_type = field_type, field_storage_type = "memory" if util.is_complex_type(field_type) else "" ) @@ -225,8 +239,115 @@ def gen_global_enum(file: FileDescriptor, delegate_codecs: List[str]): enum_definition = gen_enum_definition(file), )) +def gen_contract_interface(service, delegate_codecs: List[str]): + """Generate Solidity contract interface from proto service definition. + + Converts proto service: + service ContractInterface { + rpc registerIdentity(RegisterIdentityRequest) returns (RegisterIdentityResponse); + } + + To Solidity interface: + interface IContractInterface { + function registerIdentity(string memory domain, string memory identifier) external returns (bool success, string memory error, string memory transaction_hash); + } + """ + from google.protobuf.descriptor import ServiceDescriptor + + # Generate interface name from service name + interface_name = f"I{service.name}" + + # Generate function signatures for each RPC method + functions = [] + for method in service.methods: + # Convert method name to camelCase + function_name = method.name[0].lower() + method.name[1:] if method.name else method.name + + # Get input and output message types + input_msg = method.input_type + output_msg = method.output_type + + # Generate function parameters from input message fields + params = [] + for field in input_msg.fields: + solidity_type = get_solidity_type_for_field(field) + param_name = field.name + params.append(f"{solidity_type} {param_name}") + + # Generate return values from output message fields + returns = [] + for field in output_msg.fields: + solidity_type = get_solidity_type_for_field(field) + return_name = field.name + returns.append(f"{solidity_type} {return_name}") + + # Construct function signature + param_str = ", ".join(params) + return_str = f" returns ({', '.join(returns)})" if returns else "" + function_sig = f" function {function_name}({param_str}) external{return_str};" + functions.append(function_sig) + + # Generate the complete interface + interface_code = f""" +// Generated interface for proto service: {service.name} +interface {interface_name} {{ +{chr(10).join(functions)} +}} +""" + + delegate_codecs.append(interface_code) + +def get_solidity_type_for_field(field): + """Convert protobuf field type to Solidity type.""" + from google.protobuf.descriptor import FieldDescriptor + + # Handle repeated fields + if field.label == FieldDescriptor.LABEL_REPEATED: + base_type = get_solidity_base_type(field) + return f"{base_type}[] memory" + + # Handle message types (complex types) + if field.type == FieldDescriptor.TYPE_MESSAGE: + # For complex messages, we need to use the generated struct type + message_name = field.message_type.name + return f"{message_name} memory" + + # Handle basic types with memory if needed + base_type = get_solidity_base_type(field) + if base_type in ["string", "bytes"]: + return f"{base_type} memory" + return base_type + +def get_solidity_base_type(field): + """Get base Solidity type for a protobuf field.""" + from google.protobuf.descriptor import FieldDescriptor + + if field.type == FieldDescriptor.TYPE_STRING: + return "string" + elif field.type == FieldDescriptor.TYPE_BOOL: + return "bool" + elif field.type == FieldDescriptor.TYPE_UINT32: + return "uint32" + elif field.type == FieldDescriptor.TYPE_UINT64: + return "uint256" # Use uint256 for uint64 in Solidity + elif field.type == FieldDescriptor.TYPE_BYTES: + return "bytes" + elif field.type == FieldDescriptor.TYPE_MESSAGE: + # Generate the full library path for protobuf message types + message_name = field.message_type.name + # Use the existing utility function to generate the proper library name + import gen_util as util + lib_name = util.gen_delegate_lib_name(field.message_type) + return f"{lib_name}.Data" + else: + # For other types, use string as fallback + return "string" + RUNTIME_FILE_NAME = "ProtoBufRuntime.sol" PROTOBUF_ANY_FILE_NAME = "GoogleProtobufAny.sol" +PROTOBUF_DURATION_FILE_NAME = "GoogleProtobufDuration.sol" +PROTOBUF_TIMESTAMP_FILE_NAME = "GoogleProtobufTimestamp.sol" +PROTOBUF_FIELD_DESCRIPTOR_PROTO_FILE_NAME = "GoogleProtobufFieldDescriptorProto.sol" GEN_RUNTIME = False COMPILE_META_SCHEMA = False def apply_options(params_string): @@ -236,7 +357,11 @@ def apply_options(params_string): raise ValueError('"gen_runtime" and "use_runtime" cannot be used together') if "gen_runtime" in params: GEN_RUNTIME = True - change_runtime_file_names(params["gen_runtime"]) + if isinstance(params["gen_runtime"], bool) or params["gen_runtime"] in ["true", "True", "1"]: + # Use default filename when just gen_runtime flag is provided + pass + else: + change_runtime_file_names(params["gen_runtime"]) if "use_runtime" in params: GEN_RUNTIME = False change_runtime_file_names(params["use_runtime"]) @@ -277,6 +402,8 @@ def get_location_option(f: FileDescriptor) -> str: return opts.location def extract_npm_package_name_and_directory_path(location: str) -> Tuple[str, str]: + if not location: + return "", "" parts = location.split('/') if location[0] == '@': return '/'.join(parts[:2]), '/'.join(parts[2:]) @@ -354,6 +481,9 @@ def generate_code(request, response): output.append('{0};'.format(pragma)) output.append('import "{0}";'.format(gen_import_path(RUNTIME_FILE_NAME, proto_file))) output.append('import "{0}";'.format(gen_import_path(PROTOBUF_ANY_FILE_NAME, proto_file))) + output.append('import "{0}";'.format(gen_import_path(PROTOBUF_DURATION_FILE_NAME, proto_file))) + output.append('import "{0}";'.format(gen_import_path(PROTOBUF_TIMESTAMP_FILE_NAME, proto_file))) + output.append('import "{0}";'.format(gen_import_path(PROTOBUF_FIELD_DESCRIPTOR_PROTO_FILE_NAME, proto_file))) for dep in proto_file.dependencies: if dep.package == "solidity": continue @@ -371,6 +501,10 @@ def generate_code(request, response): if len(proto_file.enum_types_by_name): gen_global_enum(proto_file, delegate_codecs) + # generate contract interfaces from services + for service in proto_file.services_by_name.values(): + gen_contract_interface(service, delegate_codecs) + # epilogue output = output + delegate_codecs diff --git a/protobuf-solidity/src/protoc/plugin/gen_util.py b/protobuf-solidity/src/protoc/plugin/gen_util.py index 00d0df0..ee32e30 100644 --- a/protobuf-solidity/src/protoc/plugin/gen_util.py +++ b/protobuf-solidity/src/protoc/plugin/gen_util.py @@ -10,8 +10,8 @@ Num2Type = { - 1: "double", - 2: "float", + 1: "bytes8", # double -> 8-byte representation + 2: "bytes4", # float -> 4-byte representation 3: "int64", # not zigzag (proto3 compiler does not seem to use it) 4: "uint64", 5: "int32", # not zigzag (proto3 compiler does not seem to use it) @@ -216,7 +216,10 @@ def parse_urllike_parameter(s): if s: for e in s.split('&'): kv = e.split('=') - ret[kv[0]] = kv[1] + if len(kv) == 1: + ret[kv[0]] = True # Boolean flag parameters + else: + ret[kv[0]] = kv[1] return ret def field_is_message(f: FieldDescriptor) -> bool: @@ -501,7 +504,41 @@ def gen_global_enum_name(file: FileDescriptor) -> str: FILE_NAME_GLOBAL_ENUMS """ - return file.name.replace(".", "_").upper() + "_" + "GLOBAL_ENUMS" + return file.name.replace(".", "_").replace("/", "_").upper() + "_" + "GLOBAL_ENUMS" + +# Solidity reserved keywords that need mangling +SOLIDITY_RESERVED_KEYWORDS = { + 'address', 'bool', 'string', 'bytes', 'byte', 'int', 'int8', 'int16', 'int24', + 'int32', 'int40', 'int48', 'int56', 'int64', 'int72', 'int80', 'int88', 'int96', + 'int104', 'int112', 'int120', 'int128', 'int136', 'int144', 'int152', 'int160', + 'int168', 'int176', 'int184', 'int192', 'int200', 'int208', 'int216', 'int224', + 'int232', 'int240', 'int248', 'int256', 'uint', 'uint8', 'uint16', 'uint24', + 'uint32', 'uint40', 'uint48', 'uint56', 'uint64', 'uint72', 'uint80', 'uint88', + 'uint96', 'uint104', 'uint112', 'uint120', 'uint128', 'uint136', 'uint144', + 'uint152', 'uint160', 'uint168', 'uint176', 'uint184', 'uint192', 'uint200', + 'uint208', 'uint216', 'uint224', 'uint232', 'uint240', 'uint248', 'uint256', + 'bytes1', 'bytes2', 'bytes3', 'bytes4', 'bytes5', 'bytes6', 'bytes7', 'bytes8', + 'bytes9', 'bytes10', 'bytes11', 'bytes12', 'bytes13', 'bytes14', 'bytes15', + 'bytes16', 'bytes17', 'bytes18', 'bytes19', 'bytes20', 'bytes21', 'bytes22', + 'bytes23', 'bytes24', 'bytes25', 'bytes26', 'bytes27', 'bytes28', 'bytes29', + 'bytes30', 'bytes31', 'bytes32', 'fixed', 'ufixed', 'abstract', 'after', + 'alias', 'apply', 'auto', 'case', 'catch', 'copyof', 'default', 'define', + 'final', 'immutable', 'implements', 'in', 'inline', 'let', 'macro', 'match', + 'mutable', 'null', 'of', 'override', 'partial', 'promise', 'reference', + 'relocatable', 'sealed', 'sizeof', 'static', 'supports', 'switch', 'typedef', + 'typeof', 'var', 'assembly', 'break', 'continue', 'contract', 'do', 'else', + 'enum', 'event', 'for', 'function', 'if', 'import', 'library', 'mapping', + 'modifier', 'pragma', 'return', 'struct', 'using', 'while', 'constant', + 'anonymous', 'indexed', 'internal', 'external', 'private', 'public', 'pure', + 'view', 'payable', 'storage', 'memory', 'calldata', 'constructor', 'fallback', + 'receive', 'virtual', 'double', 'float', 'const' +} + +def mangle_solidity_field_name(field_name: str) -> str: + """Mangle field names that conflict with Solidity reserved keywords.""" + if field_name in SOLIDITY_RESERVED_KEYWORDS: + return field_name + "_field" + return field_name def change_pb_libname_prefix(new_name: str): global PB_LIB_NAME_PREFIX @@ -549,20 +586,20 @@ def gen_visibility(is_decoder) -> str: return "public" #"internal" if is_decoder else "" def simple_term(field: FieldDescriptor) -> str: - return "r.{name}".format(name=field.name) + return "r.{name}".format(name=mangle_solidity_field_name(field.name)) def string_term(field: FieldDescriptor) -> str: - return "bytes(r.{name}).length".format(name=field.name) + return "bytes(r.{name}).length".format(name=mangle_solidity_field_name(field.name)) def bytes_term(field: FieldDescriptor) -> str: - return "r.{name}.length".format(name=field.name) + return "r.{name}.length".format(name=mangle_solidity_field_name(field.name)) def message_term(field: FieldDescriptor) -> str: child = gen_struct_name_from_field(field) - return "{child}._empty(r.{name})".format(child=child, name=field.name) + return "{child}._empty(r.{name})".format(child=child, name=mangle_solidity_field_name(field.name)) def enum_term(field: FieldDescriptor) -> str: - return "uint(r.{name})".format(name=field.name) + return "uint(r.{name})".format(name=mangle_solidity_field_name(field.name)) default_values = { "bytes": {"cond": "!= 0", "f": bytes_term}, @@ -578,6 +615,8 @@ def enum_term(field: FieldDescriptor) -> str: "fixed64": {"cond": "!= 0", "f": simple_term}, "sfixed32": {"cond": "!= 0", "f": simple_term}, "sfixed64": {"cond": "!= 0", "f": simple_term}, + "float": {"cond": "!= 0", "f": simple_term}, + "double": {"cond": "!= 0", "f": simple_term}, "enum": {"cond": "!= 0", "f": enum_term}, "message": {"cond": "!= true", "f": message_term} } @@ -589,7 +628,7 @@ def __init__(self, field: FieldDescriptor): def begin(self): if field_is_repeated(self.field): - return "if ({term} != 0) {{".format(term="r." + self.field.name + ".length") + return "if ({term} != 0) {{".format(term="r." + mangle_solidity_field_name(self.field.name) + ".length") elif self.val in default_values: dv = default_values[self.val] params = dict( @@ -600,7 +639,7 @@ def begin(self): elif is_struct_type(self.field): return "" else: - raise Exception('Unsupported type: {}', self.field.type) + raise Exception('Unsupported type: {} in field: {} from message: {}'.format(self.field.type, self.field.name, self.field.containing_type.name)) def end(self): if is_struct_type(self.field) and not field_is_repeated(self.field): @@ -635,3 +674,62 @@ def __init__(self, wrapped): @property def fields(self): return self._self_fields + +# Circular dependency handling for buf/validate types +CIRCULAR_DEPENDENCY_MAP = { + # FieldRules has circular references to MapRules and RepeatedRules + "FieldRules": { + "map": "MapRules", + "repeated": "RepeatedRules" + }, + # MapRules has circular references back to FieldRules + "MapRules": { + "keys": "FieldRules", + "values": "FieldRules" + }, + # RepeatedRules has circular reference back to FieldRules + "RepeatedRules": { + "items": "FieldRules" + } +} + +def is_circular_dependency(msg: Descriptor, field: FieldDescriptor) -> bool: + """ + Detects if a message field creates a circular dependency. + + Args: + msg: The containing message descriptor + field: The field descriptor to check + + Returns: + True if the field creates a circular dependency + """ + msg_name = msg.name + field_name = mangle_solidity_field_name(field.name) + + + # Check if this message has known circular dependencies + if msg_name in CIRCULAR_DEPENDENCY_MAP: + circular_fields = CIRCULAR_DEPENDENCY_MAP[msg_name] + if field_name in circular_fields: + # Check if the field type matches the expected circular target + if field.message_type: + target_type = field.message_type.name + expected_target = circular_fields[field_name] + return target_type == expected_target + + return False + +def should_use_bytes_for_field(msg: Descriptor, field: FieldDescriptor) -> bool: + """ + Determines if a field should use bytes instead of its natural type + to break circular dependencies. + + Args: + msg: The containing message descriptor + field: The field descriptor to check + + Returns: + True if the field should use bytes instead of its natural type + """ + return is_circular_dependency(msg, field) diff --git a/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufDuration.sol b/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufDuration.sol new file mode 100644 index 0000000..e7f4c63 --- /dev/null +++ b/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufDuration.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.10; +import "./ProtoBufRuntime.sol"; + +library GoogleProtobufDuration { + + //struct definition + struct Data { + int64 seconds_field; + int32 nanos; + } + + // Decoder section + + /** + * @dev The main decoder for memory + * @param bs The bytes array to be decoded + * @return The decoded struct + */ + function decode(bytes memory bs) internal pure returns (Data memory) { + (Data memory x, ) = _decode(32, bs, bs.length); + return x; + } + + /** + * @dev The main decoder for storage + * @param self The in-storage struct + * @param bs The bytes array to be decoded + */ + function decode(Data storage self, bytes memory bs) internal { + (Data memory x, ) = _decode(32, bs, bs.length); + store(x, self); + } + + // called by the decoder, the result should be stored in self + function store(Data memory input, Data storage self) internal { + self.seconds_field = input.seconds_field; + self.nanos = input.nanos; + } + + /** + * @dev The decoder for internal usage + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param sz The number of bytes expected + * @return The decoded struct + * @return The number of bytes decoded + */ + function _decode(uint256 p, bytes memory bs, uint256 sz) + internal + pure + returns (Data memory, uint) + { + Data memory r; + uint[3] memory counters; + uint256 fieldId; + ProtoBufRuntime.WireType wireType; + uint256 bytesRead; + uint256 offset = p; + uint256 pointer = p; + while (pointer < offset + sz) { + (fieldId, wireType, bytesRead) = ProtoBufRuntime._decode_key(pointer, bs); + pointer += bytesRead; + if (fieldId == 1) { + pointer += _read_seconds(pointer, bs, r); + } else if (fieldId == 2) { + pointer += _read_nanos(pointer, bs, r); + } else { + pointer += ProtoBufRuntime._skip_field_decode(wireType, pointer, bs); + } + } + return (r, pointer - offset); + } + + // field readers + + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_seconds( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (int64 x, uint256 sz) = ProtoBufRuntime._decode_int64(p, bs); + r.seconds_field = x; + return sz; + } + + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_nanos( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (int32 x, uint256 sz) = ProtoBufRuntime._decode_int32(p, bs); + r.nanos = x; + return sz; + } + + // Encoder section + + /** + * @dev The main encoder for memory + * @param r The struct to be encoded + * @return The encoded byte array + */ + function encode(Data memory r) internal pure returns (bytes memory) { + bytes memory bs = new bytes(_estimate(r)); + uint256 sz = _encode(r, 32, bs); + assembly { + mstore(bs, sz) + } + return bs; + } + + // inner encoder + + /** + * @dev The encoder for internal usage + * @param r The struct to be encoded + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @return The number of bytes encoded + */ + function _encode(Data memory r, uint256 p, bytes memory bs) + internal + pure + returns (uint) + { + uint256 offset = p; + uint256 pointer = p; + + if (r.seconds_field != 0) { + pointer += ProtoBufRuntime._encode_key( + 1, + ProtoBufRuntime.WireType.Varint, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_int64(r.seconds_field, pointer, bs); + } + if (r.nanos != 0) { + pointer += ProtoBufRuntime._encode_key( + 2, + ProtoBufRuntime.WireType.Varint, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_int32(r.nanos, pointer, bs); + } + return pointer - offset; + } + + // nested encoder + + /** + * @dev The encoder for inner struct + * @param r The struct to be encoded + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @return The number of bytes encoded + */ + function _encode_nested(Data memory r, uint256 p, bytes memory bs) + internal + pure + returns (uint) + { + /** + * First encoded `r` into a temporary array, and encode the actual size used. + * Then copy the temporary array into `bs`. + */ + uint256 offset = p; + uint256 pointer = p; + bytes memory tmp = new bytes(_estimate(r)); + uint256 tmpAddr = ProtoBufRuntime.getMemoryAddress(tmp); + uint256 bsAddr = ProtoBufRuntime.getMemoryAddress(bs); + uint256 size = _encode(r, 32, tmp); + pointer += ProtoBufRuntime._encode_varint(size, pointer, bs); + ProtoBufRuntime.copyBytes(tmpAddr + 32, bsAddr + pointer, size); + pointer += size; + delete tmp; + return pointer - offset; + } + + // estimator + + /** + * @dev The estimator for a struct + * @param r The struct to be encoded + * @return The number of bytes encoded in estimation + */ + function _estimate( + Data memory r + ) internal pure returns (uint) { + uint256 e = 0; + if (r.seconds_field != 0) { + e += 1 + ProtoBufRuntime._sz_int64(r.seconds_field); + } + if (r.nanos != 0) { + e += 1 + ProtoBufRuntime._sz_int32(r.nanos); + } + return e; + } + + // empty checker + + function _empty( + Data memory r + ) internal pure returns (bool) { + + if (r.seconds_field != 0) { + return false; + } + + if (r.nanos != 0) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufFieldDescriptorProto.sol b/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufFieldDescriptorProto.sol new file mode 100644 index 0000000..4d498ff --- /dev/null +++ b/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufFieldDescriptorProto.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.10; +import "./ProtoBufRuntime.sol"; + +library GoogleProtobufFieldDescriptorProto { + + enum Type { + TYPE_DOUBLE, + TYPE_FLOAT, + TYPE_INT64, + TYPE_UINT64, + TYPE_INT32, + TYPE_FIXED64, + TYPE_FIXED32, + TYPE_BOOL, + TYPE_STRING, + TYPE_GROUP, + TYPE_MESSAGE, + TYPE_BYTES, + TYPE_UINT32, + TYPE_ENUM, + TYPE_SFIXED32, + TYPE_SFIXED64, + TYPE_SINT32, + TYPE_SINT64 + } + + function decode_Type(int64 x) internal pure returns (Type) { + return Type(uint256(uint64(x))); + } + + function encode_Type(Type x) internal pure returns (int32) { + return int32(uint32(x)); + } +} \ No newline at end of file diff --git a/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufTimestamp.sol b/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufTimestamp.sol new file mode 100644 index 0000000..18ea1f1 --- /dev/null +++ b/protobuf-solidity/src/protoc/plugin/runtime/GoogleProtobufTimestamp.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.10; +import "./ProtoBufRuntime.sol"; + +library GoogleProtobufTimestamp { + + //struct definition + struct Data { + int64 seconds_field; + int32 nanos; + } + + // Decoder section + + /** + * @dev The main decoder for memory + * @param bs The bytes array to be decoded + * @return The decoded struct + */ + function decode(bytes memory bs) internal pure returns (Data memory) { + (Data memory x, ) = _decode(32, bs, bs.length); + return x; + } + + /** + * @dev The main decoder for storage + * @param self The in-storage struct + * @param bs The bytes array to be decoded + */ + function decode(Data storage self, bytes memory bs) internal { + (Data memory x, ) = _decode(32, bs, bs.length); + store(x, self); + } + + // called by the decoder, the result should be stored in self + function store(Data memory input, Data storage self) internal { + self.seconds_field = input.seconds_field; + self.nanos = input.nanos; + } + + /** + * @dev The decoder for internal usage + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param sz The number of bytes expected + * @return The decoded struct + * @return The number of bytes decoded + */ + function _decode(uint256 p, bytes memory bs, uint256 sz) + internal + pure + returns (Data memory, uint) + { + Data memory r; + uint[3] memory counters; + uint256 fieldId; + ProtoBufRuntime.WireType wireType; + uint256 bytesRead; + uint256 offset = p; + uint256 pointer = p; + while (pointer < offset + sz) { + (fieldId, wireType, bytesRead) = ProtoBufRuntime._decode_key(pointer, bs); + pointer += bytesRead; + if (fieldId == 1) { + pointer += _read_seconds(pointer, bs, r); + } else if (fieldId == 2) { + pointer += _read_nanos(pointer, bs, r); + } else { + pointer += ProtoBufRuntime._skip_field_decode(wireType, pointer, bs); + } + } + return (r, pointer - offset); + } + + // field readers + + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_seconds( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (int64 x, uint256 sz) = ProtoBufRuntime._decode_int64(p, bs); + r.seconds_field = x; + return sz; + } + + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_nanos( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (int32 x, uint256 sz) = ProtoBufRuntime._decode_int32(p, bs); + r.nanos = x; + return sz; + } + + // Encoder section + + /** + * @dev The main encoder for memory + * @param r The struct to be encoded + * @return The encoded byte array + */ + function encode(Data memory r) internal pure returns (bytes memory) { + bytes memory bs = new bytes(_estimate(r)); + uint256 sz = _encode(r, 32, bs); + assembly { + mstore(bs, sz) + } + return bs; + } + + // inner encoder + + /** + * @dev The encoder for internal usage + * @param r The struct to be encoded + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @return The number of bytes encoded + */ + function _encode(Data memory r, uint256 p, bytes memory bs) + internal + pure + returns (uint) + { + uint256 offset = p; + uint256 pointer = p; + + if (r.seconds_field != 0) { + pointer += ProtoBufRuntime._encode_key( + 1, + ProtoBufRuntime.WireType.Varint, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_int64(r.seconds_field, pointer, bs); + } + if (r.nanos != 0) { + pointer += ProtoBufRuntime._encode_key( + 2, + ProtoBufRuntime.WireType.Varint, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_int32(r.nanos, pointer, bs); + } + return pointer - offset; + } + + // nested encoder + + /** + * @dev The encoder for inner struct + * @param r The struct to be encoded + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @return The number of bytes encoded + */ + function _encode_nested(Data memory r, uint256 p, bytes memory bs) + internal + pure + returns (uint) + { + /** + * First encoded `r` into a temporary array, and encode the actual size used. + * Then copy the temporary array into `bs`. + */ + uint256 offset = p; + uint256 pointer = p; + bytes memory tmp = new bytes(_estimate(r)); + uint256 tmpAddr = ProtoBufRuntime.getMemoryAddress(tmp); + uint256 bsAddr = ProtoBufRuntime.getMemoryAddress(bs); + uint256 size = _encode(r, 32, tmp); + pointer += ProtoBufRuntime._encode_varint(size, pointer, bs); + ProtoBufRuntime.copyBytes(tmpAddr + 32, bsAddr + pointer, size); + pointer += size; + delete tmp; + return pointer - offset; + } + + // estimator + + /** + * @dev The estimator for a struct + * @param r The struct to be encoded + * @return The number of bytes encoded in estimation + */ + function _estimate( + Data memory r + ) internal pure returns (uint) { + uint256 e = 0; + if (r.seconds_field != 0) { + e += 1 + ProtoBufRuntime._sz_int64(r.seconds_field); + } + if (r.nanos != 0) { + e += 1 + ProtoBufRuntime._sz_int32(r.nanos); + } + return e; + } + + // empty checker + + function _empty( + Data memory r + ) internal pure returns (bool) { + + if (r.seconds_field != 0) { + return false; + } + + if (r.nanos != 0) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/protobuf-solidity/src/protoc/plugin/runtime/ProtoBufRuntime.sol b/protobuf-solidity/src/protoc/plugin/runtime/ProtoBufRuntime.sol index b8e9e62..b3ec112 100644 --- a/protobuf-solidity/src/protoc/plugin/runtime/ProtoBufRuntime.sol +++ b/protobuf-solidity/src/protoc/plugin/runtime/ProtoBufRuntime.sol @@ -337,6 +337,38 @@ library ProtoBufRuntime { return _decode_lendelim(p, bs); } + /** + * @dev Decode ProtoBuf float (32-bit) + * @param p The memory offset of `bs` + * @param bs The bytes array to be decoded + * @return The decoded float as bytes4 + * @return The length of `bs` used to get decoded (always 4) + */ + function _decode_float(uint256 p, bytes memory bs) + internal + pure + returns (bytes4, uint256) + { + (uint32 x, uint256 sz) = _decode_fixed32(p, bs); + return (bytes4(x), sz); + } + + /** + * @dev Decode ProtoBuf double (64-bit) + * @param p The memory offset of `bs` + * @param bs The bytes array to be decoded + * @return The decoded double as bytes8 + * @return The length of `bs` used to get decoded (always 8) + */ + function _decode_double(uint256 p, bytes memory bs) + internal + pure + returns (bytes8, uint256) + { + (uint64 x, uint256 sz) = _decode_fixed64(p, bs); + return (bytes8(uint64(x)), sz); + } + /** * @dev Decode ProtoBuf key * @param p The memory offset of `bs` @@ -837,6 +869,36 @@ library ProtoBufRuntime { return _encode_uintf(twosComplement, p, bs, 8); } + /** + * @dev Encode ProtoBuf float (32-bit) + * @param x The float as bytes4 to be encoded + * @param p The memory offset of `bs` + * @param bs The bytes array to encode to + * @return The number of bytes used to encode + */ + function _encode_float(bytes4 x, uint256 p, bytes memory bs) + internal + pure + returns (uint256) + { + return _encode_fixed32(uint32(x), p, bs); + } + + /** + * @dev Encode ProtoBuf double (64-bit) + * @param x The double as bytes8 to be encoded + * @param p The memory offset of `bs` + * @param bs The bytes array to encode to + * @return The number of bytes used to encode + */ + function _encode_double(bytes8 x, uint256 p, bytes memory bs) + internal + pure + returns (uint256) + { + return _encode_fixed64(uint64(x), p, bs); + } + /** * @dev Encode ProtoBuf fixed-length integer * @param x The unsigned integer to be encoded