|
4 | 4 | #include <gtest/gtest.h>
|
5 | 5 | #include <ledger/core/api/Networks.hpp>
|
6 | 6 | #include <spdlog/sinks/null_sink.h>
|
| 7 | +#include <wallet/bitcoin/api_impl/BitcoinLikeScriptApi.h> |
| 8 | +#include <wallet/bitcoin/api_impl/BitcoinLikeTransactionApi.h> |
| 9 | +#include <wallet/bitcoin/scripts/BitcoinLikeScript.h> |
7 | 10 | #include <wallet/bitcoin/transaction_builders/BitcoinLikeStrategyUtxoPicker.h>
|
8 | 11 | #include <wallet/common/Amount.h>
|
9 | 12 | #include <wallet/currencies.hpp>
|
@@ -40,12 +43,6 @@ class MockKeychain : public BitcoinLikeKeychain {
|
40 | 43 | MOCK_CONST_METHOD0(getOutputSizeAsSignedTxInput, int32_t());
|
41 | 44 | };
|
42 | 45 |
|
43 |
| -class MockBitcoinLikeScript : public api::BitcoinLikeScript { |
44 |
| - public: |
45 |
| - MOCK_METHOD0(head, std::shared_ptr<api::BitcoinLikeScriptChunk>()); |
46 |
| - MOCK_METHOD0(toString, std::string()); |
47 |
| -}; |
48 |
| - |
49 | 46 | class MockBitcoinLikeOutput : public api::BitcoinLikeOutput {
|
50 | 47 | public:
|
51 | 48 | MockBitcoinLikeOutput(int64_t amount) : _amount(std::make_shared<Amount>(currencies::BITCOIN, 0, BigInt(amount))){};
|
@@ -95,18 +92,20 @@ std::vector<BitcoinLikeUtxo> createUtxos(const std::vector<int64_t> &values) {
|
95 | 92 | return utxos;
|
96 | 93 | }
|
97 | 94 |
|
98 |
| -std::shared_ptr<BitcoinLikeUtxoPicker::Buddy> createBuddy(int64_t feesPerByte, int64_t outputAmount, const api::Currency ¤cy) { |
| 95 | +std::shared_ptr<BitcoinLikeUtxoPicker::Buddy> createBuddy(int64_t feesPerByte, int64_t outputAmount, const api::Currency ¤cy, const std::string keychainEngine = api::KeychainEngines::BIP32_P2PKH, const std::string address = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") { |
99 | 96 | BitcoinLikeTransactionBuildRequest r(std::make_shared<BigInt>(0));
|
100 |
| - r.wipe = false; |
101 |
| - r.feePerByte = std::make_shared<BigInt>(feesPerByte); |
102 |
| - r.outputs.push_back(std::make_tuple(std::make_shared<BigInt>(outputAmount), std::make_shared<MockBitcoinLikeScript>())); |
| 97 | + r.wipe = false; |
| 98 | + r.feePerByte = std::make_shared<BigInt>(feesPerByte); |
| 99 | + ledger::core::BitcoinLikeScript outputScript = ledger::core::BitcoinLikeScript::fromAddress(address, currency); |
| 100 | + r.outputs.push_back(std::make_tuple(std::make_shared<BigInt>(outputAmount), std::make_shared<BitcoinLikeScriptApi>(outputScript))); |
103 | 101 | r.utxoPicker = BitcoinUtxoPickerParams{api::BitcoinLikePickingStrategy::OPTIMIZE_SIZE, 0, optional<int32_t>()};
|
104 | 102 |
|
105 | 103 | BitcoinLikeGetUtxoFunction g;
|
106 | 104 | BitcoinLikeGetTxFunction tx;
|
107 | 105 | std::shared_ptr<BitcoinLikeBlockchainExplorer> e;
|
108 | 106 | auto config = std::make_shared<ledger::core::DynamicObject>();
|
109 |
| - config->putString(api::Configuration::KEYCHAIN_ENGINE, api::KeychainEngines::BIP32_P2PKH); |
| 107 | + config->putString(api::Configuration::KEYCHAIN_ENGINE, keychainEngine); |
| 108 | + config->putBoolean(api::Configuration::ALLOW_P2TR, true); |
110 | 109 | std::shared_ptr<Preferences> preferences;
|
111 | 110 | std::shared_ptr<MockKeychain> k = std::make_shared<MockKeychain>(config, currency, 0, preferences);
|
112 | 111 | static std::shared_ptr<spdlog::logger> l = spdlog::null_logger_mt("null_sink");
|
@@ -185,3 +184,63 @@ TEST(OptimizeSize, ApproximationShouldTookEnough) {
|
185 | 184 | if (buddy->changeAmount.toInt64() != 0)
|
186 | 185 | EXPECT_GE(buddy->changeAmount.toInt64(), inputSizeInBytes * feesPerByte);
|
187 | 186 | }
|
| 187 | + |
| 188 | +void feeIsEnoughFor(const std::string address, const int64_t targetOutputSizeInBytes, const int64_t feesPerByte) { |
| 189 | + const api::Currency currency = currencies::BITCOIN_TESTNET; |
| 190 | + const int64_t inputSizeInBytes = 68; // we are spending P2WPKH input |
| 191 | + |
| 192 | + const int64_t emtyTransactionSizeInBytes = 10; |
| 193 | + int64_t outputAmount = 50000000; |
| 194 | + std::vector<int64_t> inputAmounts{100000000}; |
| 195 | + |
| 196 | + auto buddy = createBuddy(feesPerByte, outputAmount, currency, api::KeychainEngines::BIP173_P2WPKH, address); |
| 197 | + |
| 198 | + const int64_t changeOutputSizeInBytes = 8 + 1 + 1 + 1 + 20; |
| 199 | + const int64_t allOutputsSizeInBytes = targetOutputSizeInBytes + changeOutputSizeInBytes; |
| 200 | + |
| 201 | + auto utxos = createUtxos(inputAmounts); |
| 202 | + auto pickedUtxos = BitcoinLikeStrategyUtxoPicker::filterWithOptimizeSize(buddy, utxos, BigInt(-1), currency); |
| 203 | + int64_t totalInputsValue = 0; |
| 204 | + for (auto utxo : pickedUtxos) { |
| 205 | + totalInputsValue += utxo.value.toLong(); |
| 206 | + } |
| 207 | + int64_t transactionFees = totalInputsValue - buddy->changeAmount.toInt64() - outputAmount; |
| 208 | + int64_t minimumRequiredFees = (emtyTransactionSizeInBytes + allOutputsSizeInBytes + inputSizeInBytes * pickedUtxos.size()) * feesPerByte; |
| 209 | + |
| 210 | + std::cerr << "transactionFees == " << transactionFees << std::endl; |
| 211 | + std::cerr << "minimumRequiredFees == " << minimumRequiredFees << std::endl; |
| 212 | + |
| 213 | + EXPECT_GE(transactionFees, minimumRequiredFees); |
| 214 | + if (buddy->changeAmount.toInt64() != 0) |
| 215 | + EXPECT_GE(buddy->changeAmount.toInt64(), inputSizeInBytes * feesPerByte); |
| 216 | +} |
| 217 | + |
| 218 | +TEST(OptimizeSize, FeeIsEnoughForP2WPKH) { |
| 219 | + const std::string address = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"; // P2WPKH |
| 220 | + |
| 221 | + const int64_t targetOutputSizeInBytes = 8 + 1 + 1 + 1 + 20; |
| 222 | + // amount + script length + witness version + hash size + hash |
| 223 | + |
| 224 | + for (int64_t feesPerByte = 1; feesPerByte < 1000000; feesPerByte *= 10) |
| 225 | + feeIsEnoughFor(address, targetOutputSizeInBytes, feesPerByte); |
| 226 | +} |
| 227 | + |
| 228 | +TEST(OptimizeSize, FeeIsEnoughForP2WSH) { |
| 229 | + const std::string address = "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"; // P2WSH |
| 230 | + |
| 231 | + const int64_t targetOutputSizeInBytes = 8 + 1 + 1 + 1 + 32; |
| 232 | + // amount + script length + witness version + hash size + hash |
| 233 | + |
| 234 | + for (int64_t feesPerByte = 1; feesPerByte < 1000000; feesPerByte *= 10) |
| 235 | + feeIsEnoughFor(address, targetOutputSizeInBytes, feesPerByte); |
| 236 | +} |
| 237 | + |
| 238 | +TEST(OptimizeSize, FeeIsEnoughForP2TR) { |
| 239 | + const std::string address = "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c"; // P2TR |
| 240 | + |
| 241 | + const int64_t targetOutputSizeInBytes = 8 + 1 + 1 + 1 + 32; |
| 242 | + // amount + script length + witness version + hash size + hash |
| 243 | + |
| 244 | + for (int64_t feesPerByte = 1; feesPerByte < 1000000; feesPerByte *= 10) |
| 245 | + feeIsEnoughFor(address, targetOutputSizeInBytes, feesPerByte); |
| 246 | +} |
0 commit comments