stack_wallet/lib/utilities/extensions/impl/contract_abi.dart
2024-05-27 18:01:41 -06:00

145 lines
4.2 KiB
Dart

/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'dart:convert';
import 'package:web3dart/web3dart.dart';
import '../../logger.dart';
extension ContractAbiExtensions on ContractAbi {
static ContractAbi fromJsonList({
required String name,
required String jsonList,
}) {
try {
final List<ContractFunction> functions = [];
final List<ContractEvent> events = [];
final list =
List<Map<String, dynamic>>.from(jsonDecode(jsonList) as List);
for (final json in list) {
final type = json["type"] as String;
final name = json["name"] as String? ?? "";
if (type == "event") {
final anonymous = json["anonymous"] as bool? ?? false;
final List<EventComponent<dynamic>> components = [];
if (json["inputs"] is List) {
for (final input in json["inputs"] as List) {
components.add(
EventComponent(
_parseParam(input as Map),
input['indexed'] as bool? ?? false,
),
);
}
}
events.add(ContractEvent(anonymous, name, components));
} else {
final mutability = _mutabilityNames[json['stateMutability']];
final parsedType = _functionTypeNames[json['type']];
if (parsedType != null) {
final inputs = _parseParams(json['inputs'] as List?);
final outputs = _parseParams(json['outputs'] as List?);
functions.add(
ContractFunction(
name,
inputs,
outputs: outputs,
type: parsedType,
mutability: mutability ?? StateMutability.nonPayable,
),
);
}
}
}
return ContractAbi(name, functions, events);
} catch (e, s) {
Logging.instance.log(
"Failed to parse ABI for $name: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
static const Map<String, ContractFunctionType> _functionTypeNames = {
'function': ContractFunctionType.function,
'constructor': ContractFunctionType.constructor,
'fallback': ContractFunctionType.fallback,
};
static const Map<String, StateMutability> _mutabilityNames = {
'pure': StateMutability.pure,
'view': StateMutability.view,
'nonpayable': StateMutability.nonPayable,
'payable': StateMutability.payable,
};
static List<FunctionParameter<dynamic>> _parseParams(List<dynamic>? data) {
if (data == null || data.isEmpty) return [];
final elements = <FunctionParameter<dynamic>>[];
for (final entry in data) {
elements.add(_parseParam(entry as Map));
}
return elements;
}
static FunctionParameter<dynamic> _parseParam(Map<dynamic, dynamic> entry) {
final name = entry['name'] as String;
final typeName = entry['type'] as String;
if (typeName.contains('tuple')) {
final components = entry['components'] as List;
return _parseTuple(name, typeName, _parseParams(components));
} else {
final type = parseAbiType(entry['type'] as String);
return FunctionParameter(name, type);
}
}
static CompositeFunctionParameter _parseTuple(
String name,
String typeName,
List<FunctionParameter<dynamic>> components,
) {
// The type will have the form tuple[3][]...[1], where the indices after the
// tuple indicate that the type is part of an array.
assert(
RegExp(r'^tuple(?:\[\d*\])*$').hasMatch(typeName),
'$typeName is an invalid tuple type',
);
final arrayLengths = <int?>[];
var remainingName = typeName;
while (remainingName != 'tuple') {
final arrayMatch = RegExp(r'^(.*)\[(\d*)\]$').firstMatch(remainingName)!;
remainingName = arrayMatch.group(1)!;
final insideSquareBrackets = arrayMatch.group(2)!;
if (insideSquareBrackets.isEmpty) {
arrayLengths.insert(0, null);
} else {
arrayLengths.insert(0, int.parse(insideSquareBrackets));
}
}
return CompositeFunctionParameter(name, components, arrayLengths);
}
}