diff --git a/Makefile b/Makefile index c9b33a8..98e441d 100644 --- a/Makefile +++ b/Makefile @@ -108,4 +108,4 @@ web_build_and_host: file_test: @reset - @flutter test test/shared/widgets/mobile_footer_tests.dart \ No newline at end of file + @flutter test test/shared/widgets/mobile_footer_tests.dart diff --git a/lib/blocks/providers/block_provider.dart b/lib/blocks/providers/block_provider.dart index acefc82..e54efd9 100644 --- a/lib/blocks/providers/block_provider.dart +++ b/lib/blocks/providers/block_provider.dart @@ -348,15 +348,27 @@ class BlockNotifier extends StateNotifier>> { } } -//TODO: figure out a better way since a ton of empty blocks means this is taking too long final getFirstPopulatedBlockProvider = FutureProvider.autoDispose.family((ref, selectedChain) async { int depth = 0; final genusClient = ref.read(genusProvider(selectedChain)); var nextBlock = await genusClient.getBlockByDepth(depth: depth); + // Get the current time + final startTime = DateTime.now(); + //check that block has transactions while (!nextBlock.block.fullBody.hasField(1)) { + // This will break the loop after 20 seconds + // Get the current time in each iteration + final currentTime = DateTime.now(); + // Calculate the elapsed time + final elapsedTime = currentTime.difference(startTime); + // If the elapsed time is greater than the desired duration, break the loop + if (elapsedTime > const Duration(seconds: 20)) { + throw ('Error in blockProvider: getFirstPopulatedBlockProvider took too long'); + } + depth++; nextBlock = await genusClient.getBlockByDepth(depth: depth); } diff --git a/lib/transactions/providers/transactions_provider.dart b/lib/transactions/providers/transactions_provider.dart index c04f3b7..ba9d13d 100644 --- a/lib/transactions/providers/transactions_provider.dart +++ b/lib/transactions/providers/transactions_provider.dart @@ -89,7 +89,7 @@ class TransactionsNotifier extends StateNotifier>> final Ref ref; final Chains selectedChain; TransactionsNotifier(this.ref, this.selectedChain) : super(const AsyncLoading()) { - getTransactions(setState: true); + getTransactions(); } /// Gets a transaction from genus and adds it to state @@ -163,67 +163,68 @@ class TransactionsNotifier extends StateNotifier>> /// /// If [setState] is true, it will update the state of the provider /// If [setState] is false, it will not update the state of the provider - Future> getTransactions({ - bool setState = false, - }) async { + Future getTransactions() async { if (selectedChain == const Chains.mock()) { final transactions = List.generate(100, (index) => getMockTransaction()); - if (setState) state = AsyncData(transactions); - return transactions; + state = AsyncData(transactions); } else { - if (setState) state = const AsyncLoading(); + state = const AsyncLoading(); final List transactions = []; //get first populated block - - var latestBlockRes = await ref.read(getFirstPopulatedBlockProvider(selectedChain).future); - - final config = ref.read(configProvider.future); - final presentConfig = await config; - - int transactionCount = latestBlockRes.block.fullBody.transactions.length; - - var latestBlock = Block( - header: decodeId(latestBlockRes.block.header.headerId.value), - epoch: latestBlockRes.block.header.slot.toInt() ~/ presentConfig.config.epochLength.toInt(), - size: latestBlockRes.writeToBuffer().lengthInBytes.toDouble(), - height: latestBlockRes.block.header.height.toInt(), - slot: latestBlockRes.block.header.slot.toInt(), - timestamp: latestBlockRes.block.header.timestamp.toInt(), - transactionNumber: transactionCount, - ); - - //continue going through transactions - for (int i = 0; i < transactionCount; i++) { - //calculate transaction amount - var outputList = latestBlockRes.block.fullBody.transactions[i].outputs.toList(); - var inputList = latestBlockRes.block.fullBody.transactions[i].inputs.toList(); - var txAmount = calculateAmount(outputs: outputList); - var txFees = calculateFees(inputs: inputList, outputs: outputList); - - transactions.add( - Transaction( - transactionId: decodeId(latestBlockRes.block.fullBody.transactions[i].transactionId.value), - status: TransactionStatus.pending, - block: latestBlock, - broadcastTimestamp: latestBlock.timestamp, - confirmedTimestamp: 0, //for the latest block, it will never be confirmed (confirmation depth is 5) - transactionType: TransactionType.transfer, - amount: txAmount.toDouble(), - quantity: txAmount.toDouble(), - transactionFee: txFees.toDouble(), - senderAddress: - latestBlockRes.block.fullBody.transactions[i].inputs.map((e) => decodeId(e.address.id.value)).toList(), - receiverAddress: - latestBlockRes.block.fullBody.transactions[i].outputs.map((e) => decodeId(e.address.id.value)).toList(), - transactionSize: latestBlockRes.block.fullBody.transactions[i].writeToBuffer().lengthInBytes.toDouble(), - name: latestBlockRes.block.fullBody.transactions[i].inputs[0].value.hasLvl() ? 'Lvl' : 'Topl', - ), + try { + var latestBlockRes = await ref.read(getFirstPopulatedBlockProvider(selectedChain).future); + final config = ref.read(configProvider.future); + final presentConfig = await config; + int transactionCount = latestBlockRes.block.fullBody.transactions.length; + var latestBlock = Block( + header: decodeId(latestBlockRes.block.header.headerId.value), + epoch: latestBlockRes.block.header.slot.toInt() ~/ presentConfig.config.epochLength.toInt(), + size: latestBlockRes.writeToBuffer().lengthInBytes.toDouble(), + height: latestBlockRes.block.header.height.toInt(), + slot: latestBlockRes.block.header.slot.toInt(), + timestamp: latestBlockRes.block.header.timestamp.toInt(), + transactionNumber: transactionCount, ); - } - if (setState) { + //continue going through transactions + for (int i = 0; i < transactionCount; i++) { + //calculate transaction amount + var outputList = latestBlockRes.block.fullBody.transactions[i].outputs.toList(); + var inputList = latestBlockRes.block.fullBody.transactions[i].inputs.toList(); + var txAmount = calculateAmount(outputs: outputList); + var txFees = calculateFees(inputs: inputList, outputs: outputList); + + final name = latestBlockRes.block.fullBody.transactions[i].inputs.isNotEmpty && + latestBlockRes.block.fullBody.transactions[i].inputs[0].value.hasLvl() + ? 'Lvl' + : 'Topl'; + + transactions.add( + Transaction( + transactionId: decodeId(latestBlockRes.block.fullBody.transactions[i].transactionId.value), + status: TransactionStatus.pending, + block: latestBlock, + broadcastTimestamp: latestBlock.timestamp, + confirmedTimestamp: 0, //for the latest block, it will never be confirmed (confirmation depth is 5) + transactionType: TransactionType.transfer, + amount: txAmount.toDouble(), + quantity: txAmount.toDouble(), + transactionFee: txFees.toDouble(), + senderAddress: latestBlockRes.block.fullBody.transactions[i].inputs + .map((e) => decodeId(e.address.id.value)) + .toList(), + receiverAddress: latestBlockRes.block.fullBody.transactions[i].outputs + .map((e) => decodeId(e.address.id.value)) + .toList(), + transactionSize: latestBlockRes.block.fullBody.transactions[i].writeToBuffer().lengthInBytes.toDouble(), + name: name, + ), + ); + } + state = AsyncData(transactions); + } catch (e) { + state = AsyncError(e, StackTrace.current); } - return transactions; } } diff --git a/lib/transactions/sections/transaction_table.dart b/lib/transactions/sections/transaction_table.dart index 8c213f0..c5a9503 100644 --- a/lib/transactions/sections/transaction_table.dart +++ b/lib/transactions/sections/transaction_table.dart @@ -168,7 +168,9 @@ class _TransactionTableScreenState extends ConsumerState ]) ])); }, - error: (error, stack) => const Text('Oops, something unexpected happened'), + error: (error, stack) { + return const Text('Oops, something unexpected happened'); + }, loading: () => const Center( child: CircularProgressIndicator(), )); diff --git a/lib/transactions/utils/utils.dart b/lib/transactions/utils/utils.dart index efccb30..86ecec1 100644 --- a/lib/transactions/utils/utils.dart +++ b/lib/transactions/utils/utils.dart @@ -41,14 +41,22 @@ List getOutputBigInts({required List outputs}) List outputLvls = outputs.where((element) { return element.value.hasLvl(); }).toList(); - - return outputLvls.map((e) { - return e.value.lvl.quantity.value.toBigInt; - }).toList(); + try { + return outputLvls.map((e) { + return e.value.lvl.quantity.value.toBigInt; + }).toList(); + } catch (e) { + return []; + } } BigInt calculateAmount({required List outputs}) { List outputBigInts = getOutputBigInts(outputs: outputs); + + if (outputBigInts.isEmpty) { + return BigInt.zero; + } + return outputBigInts.reduce((value, element) => value + element); } @@ -56,6 +64,10 @@ BigInt calculateFees({required List inputs, required Lis List inputBigInts = getInputBigInts(inputs: inputs); List outputBigInts = getOutputBigInts(outputs: outputs); + if (inputBigInts.isEmpty || outputBigInts.isEmpty) { + return BigInt.zero; + } + BigInt inputSum = inputBigInts.reduce((value, element) => value + element); BigInt outputSum = outputBigInts.reduce((value, element) => value + element); diff --git a/pubspec.yaml b/pubspec.yaml index 2b7eca5..25e3e7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: faucet -description: A new Flutter project. +description: Topl Ecosystem Faucet # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev diff --git a/test/requests/request_tokens_test.dart b/test/requests/request_tokens_test.dart new file mode 100644 index 0000000..8253be0 --- /dev/null +++ b/test/requests/request_tokens_test.dart @@ -0,0 +1,65 @@ +import 'package:faucet/home/sections/get_test_tokens.dart'; +import 'package:faucet/requests/providers/requests_provider.dart'; +import 'package:faucet/shared/services/hive/hive_service.dart'; +import 'package:faucet/transactions/sections/transaction_table.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../essential_test_provider_widget.dart'; +import '../required_test_class.dart'; +import 'utils/mock_request_hive_utils.dart'; + +class RequiredRequestTokensTest extends RequiredTest { + Future Function(TestScreenSizes testScreenSize) requestTokenTest; + + RequiredRequestTokensTest({ + required this.requestTokenTest, + required super.testScreenSize, + }); + + Future runTests() async { + await requestTokenTest(testScreenSize); + } +} + +void main() async { + final requestTests = RequiredRequestTokensTest( + requestTokenTest: (testScreenSize) => requestTokenTest(testScreenSize), + testScreenSize: TestScreenSizes.desktop, + ); + + await requestTests.runTests(); +} + +Future requestTokenTest(TestScreenSizes testScreenSize) async => + testWidgets('Should confirm that tokens are requested', (WidgetTester tester) async { + await tester.pumpWidget( + await essentialTestProviderWidget( + tester: tester, + testScreenSize: testScreenSize, + overrides: [hivePackageProvider.overrideWithValue(getMockRequestHive().mockHive)], + ), + ); + await tester.pumpAndSettle(); + // click request token button + await tester.ensureVisible(find.byKey(TransactionTableScreen.requestTokensKey)); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(TransactionTableScreen.requestTokensKey)); + await tester.pumpAndSettle(); + // check that the drawer is displayed + expect(find.byKey(GetTestTokens.getTestTokensKey), findsOneWidget); + // click the request token button + var requestTokenButton = find.byKey(GetTestTokens.requestTokenButtonKey); + + await tester.ensureVisible(requestTokenButton); + await tester.pumpAndSettle(); + await tester.tap(requestTokenButton); + await tester.pumpAndSettle(); + + bool successDialogIsDisplayed = find.byKey(SuccessDialog.requestSuccessDialogKey).evaluate().isNotEmpty; + + if (successDialogIsDisplayed) { + //Show success dialog + expect(find.byKey(SuccessDialog.requestSuccessDialogKey), findsOneWidget); + await tester.pumpAndSettle(); + } + }); diff --git a/web/favicon.png b/web/favicon.png index 8aaa46a..5c73517 100644 Binary files a/web/favicon.png and b/web/favicon.png differ