LCOV - code coverage report
Current view: top level - crypto/evm/entities/contract - contract_function.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 56.2 % 235 132
Test Date: 2025-01-30 01:10:00 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:convert';
       2              : import 'dart:typed_data';
       3              : import 'package:walletkit_dart/src/common/logger.dart';
       4              : import 'package:walletkit_dart/src/crypto/evm/entities/contract/contract_function_encoding.dart';
       5              : import 'package:walletkit_dart/src/crypto/evm/entities/contract/contract_function_decoding.dart';
       6              : import 'package:walletkit_dart/src/utils/keccak.dart';
       7              : import 'package:walletkit_dart/walletkit_dart.dart';
       8              : 
       9              : sealed class ContractFunction implements ExternalContractFunctionMixin {
      10              :   final String name;
      11              : 
      12            8 :   const ContractFunction({
      13              :     required this.name,
      14              :   });
      15              : 
      16            0 :   bool get isExternal => !isLocal;
      17              : 
      18            0 :   bool get isLocal => this is LocalContractFunctionMixin;
      19              : 
      20              :   List<FunctionParam> get parameters;
      21              : 
      22            7 :   String get function {
      23           39 :     final params = parameters.map((e) => e.type.name).join(',');
      24           14 :     return "$name($params)";
      25              :   }
      26              : 
      27            7 :   Uint8List get functionSelector {
      28           14 :     final buffer = keccakUtf8(function);
      29            7 :     return buffer.sublist(0, 4);
      30              :   }
      31              : 
      32            7 :   String get functionSelectorHex {
      33           14 :     return functionSelector.toHex;
      34              :   }
      35              : 
      36            0 :   ContractFunctionWithValues addValues({
      37              :     required List<dynamic> values,
      38              :   }) {
      39            0 :     assert(values.length == parameters.length, "Provided values are invalid");
      40              : 
      41            0 :     final paramsWithValues = <FunctionParamWithValue>[];
      42            0 :     for (var i = 0; i < parameters.length; i++) {
      43            0 :       final param = parameters[i];
      44            0 :       final value = values[i];
      45              : 
      46              :       // if (param.type.internalType != value.runtimeType) {
      47              :       //   if (param.type.internalType == BigInt &&
      48              :       //       value.runtimeType.toString() == "_BigIntImpl") {
      49              :       //   } else
      50              :       //     throw Exception(
      51              :       //       "Invalid type for param: ${param.name}. Expected: ${param.type.internalType} Got: ${value.runtimeType}",
      52              :       //     );
      53              :       // }
      54              : 
      55            0 :       final paramWithValue = FunctionParamWithValue.fromParam(param, value);
      56            0 :       paramsWithValues.add(paramWithValue);
      57              :     }
      58              : 
      59              :     return switch (this) {
      60            0 :       ErrorContractFunctionMixin() =>
      61            0 :         throw Exception("Cannot add values to $this"),
      62            0 :       LocalContractFunctionMixin functionMixin =>
      63            0 :         LocalContractFunctionWithValues(
      64            0 :           name: name,
      65              :           parameters: paramsWithValues,
      66            0 :           stateMutability: functionMixin.stateMutability,
      67            0 :           outputTypes: functionMixin.outputTypes,
      68              :         ),
      69            0 :       ExternalContractFunctionMixin _ => ExternalContractFunctionWithValues(
      70            0 :           name: name,
      71              :           parameters: paramsWithValues,
      72              :         ),
      73              :     };
      74              :   }
      75              : 
      76            4 :   static ExternalContractFunction? fromTextSignature({
      77              :     required String textSignature,
      78              :   }) {
      79            4 :     final opening = textSignature.indexOf("(");
      80            4 :     final closing = textSignature.lastIndexOf(")");
      81              : 
      82           16 :     if (opening == -1 || closing == -1) {
      83              :       return null;
      84              :     }
      85              : 
      86              :     try {
      87            4 :       final name = textSignature.substring(0, opening);
      88            4 :       final params_s = extractParams(
      89            8 :         textSignature.substring(opening + 1, closing),
      90              :       );
      91            4 :       final params = [
      92            4 :         for (final (type_s, name) in params_s)
      93            4 :           FunctionParam(
      94              :             name: name,
      95            4 :             type: FunctionParamType.fromString(type_s),
      96              :           ),
      97              :       ];
      98              : 
      99            4 :       return ExternalContractFunction(
     100              :         name: name,
     101              :         parameters: params,
     102              :       );
     103              :     } catch (e, s) {
     104            1 :       Logger.logError(
     105              :         e,
     106              :         s: s,
     107            1 :         hint: "Failed to parse function text signature: $textSignature",
     108              :       );
     109              :       return null;
     110              :     }
     111              :   }
     112              : 
     113            1 :   Json toJson() {
     114            1 :     return {
     115            1 :       "name": name,
     116            5 :       "parameters": parameters.map((e) => e.toJson()).toList(),
     117              :     };
     118              :   }
     119              : 
     120            0 :   factory ContractFunction.fromJson(Map json) {
     121              :     if (json
     122              :         case {
     123            0 :           "name": String name,
     124            0 :           "parameters": List<dynamic> parameters,
     125              :         }) {
     126            0 :       return ExternalContractFunction(
     127              :         name: name,
     128            0 :         parameters: [
     129            0 :           for (final param in parameters) FunctionParam.fromJson(param),
     130              :         ],
     131              :       );
     132              :     }
     133            0 :     if (json case {"name": String name, "data": String data}) {
     134            0 :       final dataBytes = data.hexToBytes;
     135            0 :       final timestamp = json["timeStamp"] as int?;
     136              :       return switch (name) {
     137            0 :         "Unknown" =>
     138            0 :           UnknownContractFunction(data: dataBytes, timeStamp: timestamp),
     139            0 :         "NotDecodable" =>
     140            0 :           NotDecodableContractFunction(data: dataBytes, timeStamp: timestamp),
     141            0 :         _ => throw Exception("Invalid json"),
     142              :       };
     143              :     }
     144              : 
     145            0 :     throw Exception("Invalid json");
     146              :   }
     147              : 
     148              :   ///
     149              :   /// Try to decode the raw data using the [abiList]
     150              :   /// If the function is not found locally it will try to fetch the function from an external source (4byte.directory)
     151              :   /// If the function is not found, return null
     152              :   /// If a local function is found, return a [LocalContractFunctionWithValues]
     153              :   /// If an external function is found, return a [ExternalContractFunctionWithValues]
     154              :   ///
     155            1 :   static Future<ContractFunctionWithValues> decodeRawWithFetch({
     156              :     required Uint8List data,
     157              :     Map<String, String>? functionMap,
     158              :     bool openChain = true,
     159              :     bool fourByte = true,
     160              :   }) async {
     161            2 :     if (data.length < 4) return UnknownContractFunction(data: data);
     162              : 
     163              :     if (functionMap != null) {
     164            1 :       final localResult = decodeRaw(data: data, functionMap: functionMap);
     165              : 
     166            1 :       if (localResult is! UnknownContractFunction &&
     167            0 :           localResult is! NotDecodableContractFunction) {
     168              :         return localResult;
     169              :       }
     170              :     }
     171              : 
     172            2 :     final function_selector = data.sublist(0, 4).toHex;
     173              : 
     174              :     try {
     175            2 :       final externalFunction = await FunctionSelectorRepository().fetchSelector(
     176              :         function_selector,
     177              :         openChain: openChain,
     178              :         fourByte: fourByte,
     179              :       );
     180              :       if (externalFunction == null) {
     181            0 :         return UnknownContractFunction(data: data);
     182              :       }
     183              : 
     184            1 :       return decode(data: data, function: externalFunction);
     185              :     } catch (e) {
     186            0 :       return UnknownContractFunction(data: data);
     187              :     }
     188              :   }
     189              : 
     190              :   ///
     191              :   /// Try to decode the raw data using the [abiList] and return the decoded function
     192              :   /// If the function is not found, return null
     193              :   ///
     194            2 :   static ContractFunctionWithValues decodeRaw({
     195              :     required Uint8List data,
     196              :     required Map<String, String> functionMap,
     197              :   }) {
     198            4 :     if (data.length < 4) return UnknownContractFunction(data: data);
     199              : 
     200            6 :     final hex_signature = "0x${data.sublist(0, 4).toHex}";
     201              : 
     202            2 :     final text_signarure = functionMap[hex_signature];
     203              : 
     204            1 :     if (text_signarure == null) return UnknownContractFunction(data: data);
     205              : 
     206              :     final function =
     207            1 :         ContractFunction.fromTextSignature(textSignature: text_signarure);
     208              : 
     209            0 :     if (function == null) return UnknownContractFunction(data: data);
     210              : 
     211            1 :     return ContractFunction.decode(
     212              :       data: data,
     213              :       function: function,
     214              :     );
     215              :   }
     216              : 
     217              :   ///
     218              :   /// Try to decode the raw data using the [function] and return the decoded function
     219              :   /// If the decoding of the data with information from the [function] fails a [NotDecodableContractFunction] is returned
     220              :   ///
     221            4 :   static ContractFunctionWithValues decode({
     222              :     required Uint8List data,
     223              :     required ContractFunction function,
     224              :   }) {
     225              :     try {
     226            8 :       if (data.length < 4) {
     227            0 :         throw FunctionDecodingException("Invalid data length: ${data.length}");
     228              :       }
     229              : 
     230            8 :       final function_selector = data.sublist(0, 4).toHex;
     231              : 
     232            8 :       if (function_selector != function.functionSelectorHex) {
     233            1 :         throw FunctionSelectorException(
     234            1 :           "Invalid function selector: $function_selector",
     235              :         );
     236              :       }
     237              : 
     238            4 :       var dataWithoutSelector = data.sublist(4);
     239              : 
     240           12 :       if (dataWithoutSelector.length % 32 != 0) {
     241            1 :         dataWithoutSelector = dataWithoutSelector.sublist(
     242              :           0,
     243            4 :           dataWithoutSelector.length - (dataWithoutSelector.length % 32),
     244              :         );
     245              :       }
     246              : 
     247            4 :       final decodedParams = decodeDataField(
     248              :         data: dataWithoutSelector,
     249            4 :         params: function.parameters,
     250              :       );
     251              : 
     252            4 :       if (function is LocalContractFunction) {
     253            1 :         return LocalContractFunctionWithValues(
     254            1 :           name: function.name,
     255              :           parameters: decodedParams,
     256            1 :           stateMutability: function.stateMutability,
     257            1 :           outputTypes: function.outputTypes,
     258              :         );
     259              :       }
     260              : 
     261            3 :       return ExternalContractFunctionWithValues(
     262            3 :         name: function.name,
     263              :         parameters: decodedParams,
     264              :       );
     265              :     } catch (e) {
     266            1 :       return NotDecodableContractFunction(
     267              :         data: data,
     268              :         error: e,
     269              :       );
     270              :     }
     271              :   }
     272              : }
     273              : 
     274              : sealed class ContractFunctionWithValues extends ContractFunction {
     275            7 :   const ContractFunctionWithValues({
     276              :     required super.name,
     277              :   });
     278              : 
     279              :   @override
     280              :   List<FunctionParamWithValue> get parameters;
     281              : 
     282            3 :   Uint8List buildDataField() {
     283            3 :     final dataFieldBuilder = DataFieldBuilder.fromFunction(function: this);
     284            3 :     return dataFieldBuilder.buildDataField();
     285              :   }
     286              : 
     287            1 :   factory ContractFunctionWithValues.fromJson(Map json) {
     288              :     if (json
     289              :         case {
     290            2 :           "name": String name,
     291            2 :           "parameters": List<dynamic> parameters,
     292              :         }) {
     293            1 :       return ExternalContractFunctionWithValues(
     294              :         name: name,
     295            1 :         parameters: [
     296            1 :           for (final param in parameters)
     297            1 :             FunctionParamWithValue.fromJson(param),
     298              :         ],
     299              :       );
     300              :     }
     301            0 :     if (json case {"name": String name, "data": String data}) {
     302            0 :       final dataBytes = data.hexToBytes;
     303              :       return switch (name) {
     304            0 :         "Unknown" => UnknownContractFunction(data: dataBytes),
     305            0 :         "NotDecodable" => NotDecodableContractFunction(data: dataBytes),
     306            0 :         _ => throw Exception("Invalid json"),
     307              :       };
     308              :     }
     309              : 
     310            0 :     throw Exception("Invalid json");
     311              :   }
     312              : }
     313              : 
     314              : sealed class ContractFunctionWithValuesAndOutputs
     315              :     extends ContractFunctionWithValues {
     316              :   List<FunctionParamWithValue> get outputs;
     317              : 
     318              :   List<FunctionParamWithValue> get parameters;
     319              : 
     320            3 :   const ContractFunctionWithValuesAndOutputs({
     321              :     required super.name,
     322              :   });
     323              : }
     324              : 
     325              : final class LocalContractFunctionWithValuesAndOutputs
     326              :     extends ContractFunctionWithValuesAndOutputs
     327              :     implements LocalContractFunctionMixin {
     328              :   @override
     329              :   final List<FunctionParamWithValue> outputs;
     330              : 
     331            0 :   @override
     332            0 :   List<FunctionParam> get outputTypes => outputs;
     333              : 
     334              :   @override
     335              :   final List<FunctionParamWithValue> parameters;
     336              : 
     337              :   @override
     338              :   final StateMutability stateMutability;
     339              : 
     340            3 :   factory LocalContractFunctionWithValuesAndOutputs.decode({
     341              :     required LocalContractFunctionWithValues function,
     342              :     required Uint8List data,
     343              :   }) {
     344            3 :     final decodedOutputs = decodeDataField(
     345              :       data: data,
     346            3 :       params: function.outputTypes,
     347              :     );
     348              : 
     349            3 :     return LocalContractFunctionWithValuesAndOutputs(
     350            3 :       name: function.name,
     351            3 :       parameters: function.parameters,
     352              :       outputs: decodedOutputs,
     353            3 :       stateMutability: function.stateMutability,
     354              :     );
     355              :   }
     356              : 
     357            3 :   const LocalContractFunctionWithValuesAndOutputs({
     358              :     required super.name,
     359              :     required this.outputs,
     360              :     required this.parameters,
     361              :     required this.stateMutability,
     362              :   });
     363              : }
     364              : 
     365              : final class ExternalContractFunctionWithValuesAndOutputs
     366              :     extends ContractFunctionWithValuesAndOutputs
     367              :     implements ExternalContractFunctionMixin {
     368              :   @override
     369              :   final List<FunctionParamWithValue> outputs;
     370              : 
     371              :   @override
     372              :   final List<FunctionParamWithValue> parameters;
     373              : 
     374              :   @override
     375              :   final StateMutability? stateMutability;
     376              : 
     377              :   @override
     378              :   final List<FunctionParam>? outputTypes;
     379              : 
     380            0 :   factory ExternalContractFunctionWithValuesAndOutputs.decode({
     381              :     required ExternalContractFunctionWithValues function,
     382              :     required List<FunctionParam> outputs,
     383              :     required Uint8List data,
     384              :     StateMutability? stateMutability,
     385              :   }) {
     386            0 :     final decodedOutputs = decodeDataField(
     387              :       data: data,
     388              :       params: outputs,
     389              :     );
     390              : 
     391            0 :     return ExternalContractFunctionWithValuesAndOutputs._(
     392            0 :       name: function.name,
     393            0 :       parameters: function.parameters,
     394              :       outputs: decodedOutputs,
     395              :       outputTypes: outputs,
     396              :       stateMutability: stateMutability,
     397              :     );
     398              :   }
     399              : 
     400            0 :   const ExternalContractFunctionWithValuesAndOutputs._({
     401              :     required super.name,
     402              :     required this.outputs,
     403              :     required this.parameters,
     404              :     this.stateMutability,
     405              :     this.outputTypes,
     406              :   });
     407              : }
     408              : 
     409              : ///
     410              : /// An Object that represents a contract function where we only have the function signature
     411              : /// And hence only have the function name, selector and the parameters
     412              : /// Only used for decoding existing datafields
     413              : ///
     414              : class ExternalContractFunction extends ContractFunction
     415              :     implements ExternalContractFunctionMixin {
     416              :   final List<FunctionParam> parameters;
     417              : 
     418              :   @override
     419              :   final StateMutability? stateMutability;
     420              : 
     421              :   @override
     422              :   final List<FunctionParam>? outputTypes;
     423              : 
     424            4 :   const ExternalContractFunction({
     425              :     required this.parameters,
     426              :     required super.name,
     427              :     this.stateMutability,
     428              :     this.outputTypes,
     429              :   });
     430              : }
     431              : 
     432              : ///
     433              : /// An Object that represents a contract function where we only have the function signature
     434              : /// And hence only have the function name, selector and the parameters
     435              : /// Created after decoding the datafield with [ExternalContractFunction]
     436              : ///
     437              : class ExternalContractFunctionWithValues extends ContractFunctionWithValues
     438              :     implements ExternalContractFunctionMixin {
     439              :   @override
     440              :   final List<FunctionParamWithValue> parameters;
     441              : 
     442              :   @override
     443              :   final StateMutability? stateMutability;
     444              : 
     445              :   @override
     446              :   final List<FunctionParam>? outputTypes;
     447              : 
     448            3 :   const ExternalContractFunctionWithValues({
     449              :     required super.name,
     450              :     required this.parameters,
     451              :     this.stateMutability,
     452              :     this.outputTypes,
     453              :   });
     454              : }
     455              : 
     456              : ///
     457              : /// A mixin for Locale Contract Functions
     458              : /// Used to get the stateMutability and outputs of the function
     459              : /// These are only available in the local contract functions
     460              : ///
     461              : class LocalContractFunctionMixin implements ExternalContractFunctionMixin {
     462              :   @override
     463              :   final StateMutability stateMutability;
     464              :   @override
     465              :   final List<FunctionParam> outputTypes;
     466              : 
     467            0 :   const LocalContractFunctionMixin({
     468              :     required this.stateMutability,
     469              :     required this.outputTypes,
     470              :   });
     471              : }
     472              : 
     473              : ///
     474              : /// A mixin for External Contract Functions
     475              : /// Does not have any additional properties just a marker
     476              : ///
     477              : class ExternalContractFunctionMixin {
     478              :   final StateMutability? stateMutability;
     479              :   final List<FunctionParam>? outputTypes;
     480              : 
     481            0 :   const ExternalContractFunctionMixin({
     482              :     this.stateMutability,
     483              :     this.outputTypes,
     484              :   });
     485              : }
     486              : 
     487              : class ErrorContractFunctionMixin extends ExternalContractFunctionMixin {
     488              :   /// TimeStamp when the error occured
     489              :   final int timeStamp;
     490              : 
     491            0 :   const ErrorContractFunctionMixin({
     492              :     required this.timeStamp,
     493              :   });
     494              : }
     495              : 
     496              : ///
     497              : /// An Object that represents a contract function generated from an ABI
     498              : /// Used for Encoding a datafield and after executing decoding the output
     499              : ///
     500              : class LocalContractFunction extends ContractFunction
     501              :     implements LocalContractFunctionMixin {
     502              :   @override
     503              :   final List<FunctionParam> parameters;
     504              :   final StateMutability stateMutability;
     505              :   final List<FunctionParam> outputTypes;
     506              : 
     507            4 :   const LocalContractFunction({
     508              :     required super.name,
     509              :     required this.parameters,
     510              :     required this.stateMutability,
     511              :     required this.outputTypes,
     512              :   });
     513              : 
     514            4 :   @override
     515              :   LocalContractFunctionWithValues addValues({required List<dynamic> values}) {
     516            4 :     return LocalContractFunctionWithValues(
     517            4 :       name: name,
     518            4 :       parameters: [
     519           15 :         for (var i = 0; i < parameters.length; i++)
     520           12 :           FunctionParamWithValue.fromParam(parameters[i], values[i]),
     521              :       ],
     522            4 :       stateMutability: stateMutability,
     523            4 :       outputTypes: outputTypes,
     524              :     );
     525              :   }
     526              : }
     527              : 
     528              : class LocalContractFunctionWithValues extends ContractFunctionWithValues
     529              :     implements LocalContractFunctionMixin {
     530              :   final StateMutability stateMutability;
     531              :   final List<FunctionParam> outputTypes;
     532              : 
     533              :   @override
     534              :   final List<FunctionParamWithValue> parameters;
     535              : 
     536            4 :   const LocalContractFunctionWithValues({
     537              :     required super.name,
     538              :     required this.parameters,
     539              :     required this.stateMutability,
     540              :     required this.outputTypes,
     541              :   });
     542              : }
     543              : 
     544              : class FunctionSelectorException implements Exception {
     545              :   final String message;
     546              : 
     547            1 :   FunctionSelectorException(this.message);
     548              : 
     549            0 :   @override
     550              :   String toString() {
     551            0 :     return message;
     552              :   }
     553              : }
     554              : 
     555              : class FunctionDecodingException implements Exception {
     556              :   final String message;
     557              : 
     558            0 :   FunctionDecodingException(this.message);
     559              : 
     560            0 :   @override
     561              :   String toString() {
     562            0 :     return message;
     563              :   }
     564              : }
     565              : 
     566              : class UnknownContractFunction extends ContractFunctionWithValues
     567              :     implements ErrorContractFunctionMixin {
     568              :   final Uint8List data;
     569              :   final int timeStamp;
     570            0 :   @override
     571              :   StateMutability? get stateMutability => null;
     572              : 
     573            0 :   @override
     574              :   List<FunctionParam>? get outputTypes => null;
     575              : 
     576            0 :   @override
     577              :   List<FunctionParamWithValue> get parameters {
     578            0 :     return [
     579            0 :       FunctionParamWithValue.fromParam<Uint8List>(
     580            0 :         FunctionParam(name: "data", type: FunctionParamBytes()),
     581            0 :         data,
     582              :       ),
     583              :     ];
     584              :   }
     585              : 
     586            1 :   UnknownContractFunction({
     587              :     required this.data,
     588              :     int? timeStamp,
     589            2 :   })  : timeStamp = timeStamp ?? DateTime.now().millisecondsSinceEpoch,
     590            1 :         super(name: "Unknown");
     591              : 
     592            0 :   @override
     593              :   Uint8List get functionSelector {
     594            0 :     return data.sublist(0, 4);
     595              :   }
     596              : 
     597            0 :   @override
     598              :   String get functionSelectorHex {
     599            0 :     return functionSelector.toHex;
     600              :   }
     601              : 
     602            0 :   @override
     603            0 :   String get function => name;
     604              : 
     605            0 :   String get UTF8 {
     606            0 :     return utf8.decode(data);
     607              :   }
     608              : 
     609            0 :   @override
     610              :   Json toJson() {
     611            0 :     return {
     612            0 :       "name": name,
     613            0 :       "data": data.toHex,
     614            0 :       "timeStamp": timeStamp,
     615              :     };
     616              :   }
     617              : }
     618              : 
     619              : class NotDecodableContractFunction extends ContractFunctionWithValues
     620              :     implements ErrorContractFunctionMixin {
     621              :   final Uint8List data;
     622              :   final Object? error;
     623              :   final int timeStamp;
     624              : 
     625            0 :   @override
     626              :   StateMutability? get stateMutability => null;
     627              : 
     628            0 :   @override
     629              :   List<FunctionParam>? get outputTypes => null;
     630              : 
     631            0 :   @override
     632              :   List<FunctionParamWithValue> get parameters {
     633            0 :     return [
     634            0 :       FunctionParamWithValue.fromParam<Uint8List>(
     635            0 :         FunctionParam(name: "data", type: FunctionParamBytes()),
     636            0 :         data,
     637              :       ),
     638              :     ];
     639              :   }
     640              : 
     641            1 :   NotDecodableContractFunction({
     642              :     required this.data,
     643              :     int? timeStamp,
     644              :     this.error,
     645            2 :   })  : timeStamp = timeStamp ?? DateTime.now().millisecondsSinceEpoch,
     646            1 :         super(name: "NotDecodable");
     647              : 
     648            0 :   @override
     649              :   Uint8List get functionSelector {
     650            0 :     if (data.length < 4) return Uint8List(0);
     651            0 :     return data.sublist(0, 4);
     652              :   }
     653              : 
     654            0 :   @override
     655              :   String get functionSelectorHex {
     656            0 :     return functionSelector.toHex;
     657              :   }
     658              : 
     659            0 :   @override
     660            0 :   String get function => name;
     661              : 
     662            0 :   String get UTF8 {
     663            0 :     return utf8.decode(data);
     664              :   }
     665              : 
     666            0 :   @override
     667              :   Json toJson() {
     668            0 :     return {
     669            0 :       "name": name,
     670            0 :       "data": data.toHex,
     671            0 :       "timeStamp": timeStamp,
     672              :     };
     673              :   }
     674              : }
     675              : 
     676              : extension on String {
     677            4 :   (String type, String? name) splitParam() {
     678            4 :     final string = trim();
     679              : 
     680            4 :     final splitted = string.split(' ');
     681              : 
     682            8 :     if (splitted.length != 2) return (string, null);
     683              : 
     684            6 :     return (splitted[0], splitted[1]);
     685              :   }
     686              : }
     687              : 
     688            4 : List<(String type, String? name)> extractParams(String text) {
     689            4 :   text = text.trim();
     690              : 
     691            4 :   var opening = text.indexOf("(");
     692              : 
     693            4 :   final values = <(String type, String? name)>[];
     694            8 :   final start = opening == -1
     695              :       ? text
     696            1 :       : opening == 0
     697              :           ? ""
     698            2 :           : text.substring(0, opening - 1);
     699              : 
     700            4 :   if (start.isNotEmpty) {
     701            4 :     if (start.startsWith('(')) {
     702            0 :       values.add(start.splitParam());
     703            4 :     } else if (start.contains(',')) {
     704            4 :       values.addAll(
     705           16 :         start.split(',').map((s) => s.splitParam()),
     706              :       );
     707              :     } else {
     708            6 :       values.add(start.splitParam());
     709              :     }
     710              :   }
     711            8 :   if (opening != -1) {
     712            1 :     var closing = -1;
     713              :     var nested = 0;
     714            3 :     for (var i = opening; i < text.length; i++) {
     715            1 :       final char = text[i];
     716            2 :       if (nested == 0 && char == ',') {
     717              :         closing = i;
     718              :         break;
     719              :       }
     720              : 
     721            2 :       if (char == "(") nested++;
     722            2 :       if (char == ")") nested--;
     723              :     }
     724              : 
     725            3 :     if (closing == -1) closing = text.length;
     726              : 
     727            2 :     var tuple = text.substring(opening, closing).splitParam();
     728            1 :     values.add(tuple);
     729              : 
     730            2 :     if (closing < text.length) {
     731            1 :       values.addAll(
     732            1 :         extractParams(
     733            2 :           text.substring(closing + 1),
     734              :         ),
     735              :       );
     736              :     }
     737              :   }
     738              : 
     739              :   return values;
     740              : }
        

Generated by: LCOV version 2.0-1