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

Generated by: LCOV version 2.0-1