From 87178b2a54481b4c9498c093b06002f0f2e0d5b0 Mon Sep 17 00:00:00 2001 From: cyan Date: Wed, 27 Nov 2024 16:34:36 +0100 Subject: [PATCH] CW-766 fix coin freezing (#1751) * fix coin freezing * fix frozen balance * update monero_c hash * update monero_c hash (after merge) * fix test E make it ready * revert local change * throw error on wow as well * experiment with view model code * move view model into di.dart --- cw_monero/lib/api/coins_info.dart | 12 +++++++ cw_monero/lib/api/transaction_history.dart | 7 +++- cw_monero/lib/api/wallet.dart | 10 ++++++ cw_monero/lib/monero_unspent.dart | 24 ++++++++++++- cw_monero/lib/monero_wallet.dart | 40 +++++++++------------ cw_wownero/lib/api/transaction_history.dart | 10 ++++-- lib/di.dart | 1 + lib/view_model/send/send_view_model.dart | 16 ++++++++- 8 files changed, 92 insertions(+), 28 deletions(-) diff --git a/cw_monero/lib/api/coins_info.dart b/cw_monero/lib/api/coins_info.dart index c1b634cc6b..ef7d3cfd6c 100644 --- a/cw_monero/lib/api/coins_info.dart +++ b/cw_monero/lib/api/coins_info.dart @@ -12,6 +12,18 @@ int countOfCoins() => monero.Coins_count(coins!); monero.CoinsInfo getCoin(int index) => monero.Coins_coin(coins!, index); +int? getCoinByKeyImage(String keyImage) { + final count = countOfCoins(); + for (int i = 0; i < count; i++) { + final coin = getCoin(i); + final coinAddress = monero.CoinsInfo_keyImage(coin); + if (keyImage == coinAddress) { + return i; + } + } + return null; +} + void freezeCoin(int index) => monero.Coins_setFrozen(coins!, index: index); void thawCoin(int index) => monero.Coins_thaw(coins!, index: index); diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 662eb1bb55..7c253509cc 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -6,6 +6,7 @@ import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/wallet.dart'; +import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart'; import 'package:ffi/ffi.dart'; import 'package:monero/monero.dart' as monero; import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen; @@ -101,7 +102,11 @@ Future createTransactionSync( }); final address_ = address.toNativeUtf8(); - final paymentId_ = paymentId.toNativeUtf8(); + final paymentId_ = paymentId.toNativeUtf8(); + if (preferredInputs.isEmpty) { + throw MoneroTransactionCreationException("No inputs provided, transaction cannot be constructed"); + } + final preferredInputs_ = preferredInputs.join(monero.defaultSeparatorStr).toNativeUtf8(); final addraddr = address_.address; diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 70c610418a..667a1ff696 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -4,6 +4,7 @@ import 'dart:isolate'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; +import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; import 'package:mutex/mutex.dart'; @@ -129,6 +130,15 @@ Future setupNodeSync( throw SetupWalletException(message: error); } + if (kDebugMode) { + monero.Wallet_init3( + wptr!, argv0: '', + defaultLogBaseName: 'moneroc', + console: true, + logPath: '', + ); + } + return status == 0; } diff --git a/cw_monero/lib/monero_unspent.dart b/cw_monero/lib/monero_unspent.dart index 87d8f0b392..f45fcddaff 100644 --- a/cw_monero/lib/monero_unspent.dart +++ b/cw_monero/lib/monero_unspent.dart @@ -1,10 +1,32 @@ import 'package:cw_core/unspent_transaction_output.dart'; +import 'package:cw_monero/api/coins_info.dart'; +import 'package:monero/monero.dart' as monero; class MoneroUnspent extends Unspent { MoneroUnspent( String address, String hash, String keyImage, int value, bool isFrozen, this.isUnlocked) : super(address, hash, value, 0, keyImage) { - this.isFrozen = isFrozen; + } + + @override + set isFrozen(bool freeze) { + print("set isFrozen: $freeze ($keyImage): $freeze"); + final coinId = getCoinByKeyImage(keyImage!); + if (coinId == null) throw Exception("Unable to find a coin for address $address"); + if (freeze) { + freezeCoin(coinId); + } else { + thawCoin(coinId); + } + } + + @override + bool get isFrozen { + print("get isFrozen"); + final coinId = getCoinByKeyImage(keyImage!); + if (coinId == null) throw Exception("Unable to find a coin for address $address"); + final coin = getCoin(coinId); + return monero.CoinsInfo_frozen(coin); } final bool isUnlocked; diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 5f53b30ba6..2b302e7454 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -309,9 +309,8 @@ abstract class MoneroWalletBase extends WalletBase element.walletId == id && element.accountIndex == walletAddresses.account!.id)) { - if (coin.isFrozen) frozenBalance += coin.value; + if (coin.isFrozen && !coin.isSending) frozenBalance += coin.value; } - return frozenBalance; } diff --git a/cw_wownero/lib/api/transaction_history.dart b/cw_wownero/lib/api/transaction_history.dart index 6b0923e839..ce93df7d18 100644 --- a/cw_wownero/lib/api/transaction_history.dart +++ b/cw_wownero/lib/api/transaction_history.dart @@ -6,13 +6,16 @@ import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_wownero/api/wallet.dart'; import 'package:cw_wownero/api/wownero_output.dart'; import 'package:cw_wownero/api/structs/pending_transaction.dart'; +import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dart'; import 'package:ffi/ffi.dart'; import 'package:monero/wownero.dart' as wownero; import 'package:monero/src/generated_bindings_wownero.g.dart' as wownero_gen; String getTxKey(String txId) { - return wownero.Wallet_getTxKey(wptr!, txid: txId); + final ret = wownero.Wallet_getTxKey(wptr!, txid: txId); + wownero.Wallet_status(wptr!); + return ret; } wownero.TransactionHistory? txhistory; @@ -86,7 +89,10 @@ Future createTransactionSync( final amt = amount == null ? 0 : wownero.Wallet_amountFromString(amount); final address_ = address.toNativeUtf8(); - final paymentId_ = paymentId.toNativeUtf8(); + final paymentId_ = paymentId.toNativeUtf8(); + if (preferredInputs.isEmpty) { + throw WowneroTransactionCreationException("No inputs provided, transaction cannot be constructed"); + } final preferredInputs_ = preferredInputs.join(wownero.defaultSeparatorStr).toNativeUtf8(); final waddr = wptr!.address; diff --git a/lib/di.dart b/lib/di.dart index facdec9128..be014f6658 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -746,6 +746,7 @@ Future setup({ _transactionDescriptionBox, getIt.get().wallet!.isHardwareWallet ? getIt.get() : null, coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any, + getIt.get(param1: coinTypeToSpendFrom), ), ); diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 69011aa746..69a500c9b7 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -19,6 +19,7 @@ import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_info.dart'; @@ -64,6 +65,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor wallet.type == WalletType.tron; } + UnspentCoinsListViewModel unspentCoinsListViewModel; + SendViewModelBase( AppStore appStore, this.sendTemplateViewModel, @@ -71,7 +74,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor this.balanceViewModel, this.contactListViewModel, this.transactionDescriptionBox, - this.ledgerViewModel, { + this.ledgerViewModel, + this.unspentCoinsListViewModel, { this.coinTypeToSpendFrom = UnspentCoinType.any, }) : state = InitialExecutionState(), currencies = appStore.wallet!.balance.keys.toList(), @@ -530,6 +534,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor throw Exception('Priority is null for wallet type: ${wallet.type}'); } + if (hasCoinControl) { + bool isCoinSelected = false; + for (var coin in unspentCoinsListViewModel.items) { + isCoinSelected = isCoinSelected || (coin.isSending && !coin.isFrozen); + } + if (!isCoinSelected) { + throw Exception("No coin selected in coin control, you need to select a coin in order to spend"); + } + } + switch (wallet.type) { case WalletType.bitcoin: case WalletType.litecoin: