diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 370c4b8a131..ec1c2b834f1 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -43,13 +43,17 @@ struct FeeSettingsFields std::optional baseFeeDrops = std::nullopt; std::optional reserveBaseDrops = std::nullopt; std::optional reserveIncrementDrops = std::nullopt; + std::optional extensionComputeLimit = std::nullopt; + std::optional extensionSizeLimit = std::nullopt; + std::optional gasPrice = std::nullopt; }; STTx createFeeTx( Rules const& rules, std::uint32_t seq, - FeeSettingsFields const& fields) + FeeSettingsFields const& fields, + bool forceAllFields = false) { auto fill = [&](auto& obj) { obj.setAccountID(sfAccount, AccountID()); @@ -83,6 +87,17 @@ createFeeTx( sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0); } + if (rules.enabled(featureSmartEscrow) || forceAllFields) + { + obj.setFieldU32( + sfExtensionComputeLimit, + fields.extensionComputeLimit ? *fields.extensionComputeLimit + : 0); + obj.setFieldU32( + sfExtensionSizeLimit, + fields.extensionSizeLimit ? *fields.extensionSizeLimit : 0); + obj.setFieldU32(sfGasPrice, fields.gasPrice ? *fields.gasPrice : 0); + } }; return STTx(ttFEE, fill); } @@ -131,6 +146,12 @@ createInvalidFeeTx( obj.setFieldU32(sfReserveIncrement, 50000); obj.setFieldU32(sfReferenceFeeUnits, 10); } + if (rules.enabled(featureSmartEscrow)) + { + obj.setFieldU32(sfExtensionComputeLimit, 100 + uniqueValue); + obj.setFieldU32(sfExtensionSizeLimit, 200 + uniqueValue); + obj.setFieldU32(sfGasPrice, 300 + uniqueValue); + } } // If missingRequiredFields is true, we don't add the required fields // (default behavior) @@ -138,12 +159,12 @@ createInvalidFeeTx( return STTx(ttFEE, fill); } -bool +TER applyFeeAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx) { auto const res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); - return res.ter == tesSUCCESS; + return res.ter; } bool @@ -199,6 +220,25 @@ verifyFeeObject( if (!checkEquality(sfReferenceFeeUnits, expected.referenceFeeUnits)) return false; } + if (rules.enabled(featureSmartEscrow)) + { + if (!checkEquality( + sfExtensionComputeLimit, + expected.extensionComputeLimit.value_or(0))) + return false; + if (!checkEquality( + sfExtensionSizeLimit, expected.extensionSizeLimit.value_or(0))) + return false; + if (!checkEquality(sfGasPrice, expected.gasPrice.value_or(0))) + return false; + } + else + { + if (feeObject->isFieldPresent(sfExtensionComputeLimit) || + feeObject->isFieldPresent(sfExtensionSizeLimit) || + feeObject->isFieldPresent(sfGasPrice)) + return false; + } return true; } @@ -359,7 +399,8 @@ class FeeVote_test : public beast::unit_test::suite auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); accum.apply(*ledger); // Verify fee object was created/updated correctly @@ -388,12 +429,76 @@ class FeeVote_test : public beast::unit_test::suite auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); accum.apply(*ledger); // Verify fee object was created/updated correctly BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields)); } + + // Test with both XRPFees and SmartEscrow enabled + { + jtx::Env env(*this, jtx::testable_amendments()); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + FeeSettingsFields fields{ + .baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}, + .extensionComputeLimit = 100, + .extensionSizeLimit = 200, + .gasPrice = 300}; + // Test successful fee transaction with new fields + auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); + + OpenView accum(ledger.get()); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); + accum.apply(*ledger); + + // Verify fee object was created/updated correctly + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields)); + } + + // Test that the Smart Escrow fields are rejected if the + // feature is disabled + { + jtx::Env env( + *this, jtx::testable_amendments() - featureSmartEscrow); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + FeeSettingsFields fields{ + .baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}, + .extensionComputeLimit = 100, + .extensionSizeLimit = 200, + .gasPrice = 300}; + // Test successful fee transaction with new fields + auto feeTx = + createFeeTx(ledger->rules(), ledger->seq(), fields, true); + + OpenView accum(ledger.get()); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); + } } void @@ -420,12 +525,14 @@ class FeeVote_test : public beast::unit_test::suite auto invalidTx = createInvalidFeeTx( ledger->rules(), ledger->seq(), true, false, 1); OpenView accum(ledger.get()); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); // Test transaction with new format fields when XRPFees is disabled auto disallowedTx = createInvalidFeeTx( ledger->rules(), ledger->seq(), false, true, 2); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx)); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx))); } { @@ -445,12 +552,43 @@ class FeeVote_test : public beast::unit_test::suite auto invalidTx = createInvalidFeeTx( ledger->rules(), ledger->seq(), true, false, 3); OpenView accum(ledger.get()); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); // Test transaction with legacy fields when XRPFees is enabled auto disallowedTx = createInvalidFeeTx( ledger->rules(), ledger->seq(), false, true, 4); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx)); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx))); + } + + { + jtx::Env env( + *this, + jtx::testable_amendments() | featureXRPFees | + featureSmartEscrow); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + // Test transaction with missing required new fields + auto invalidTx = createInvalidFeeTx( + ledger->rules(), ledger->seq(), true, false, 5); + OpenView accum(ledger.get()); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); + + // Test transaction with legacy fields when XRPFees is enabled + auto disallowedTx = createInvalidFeeTx( + ledger->rules(), ledger->seq(), false, true, 6); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx))); } } @@ -489,7 +627,8 @@ class FeeVote_test : public beast::unit_test::suite // But can be applied to a closed ledger { OpenView closedAccum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, closedAccum, feeTx)); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, closedAccum, feeTx))); } } @@ -516,7 +655,8 @@ class FeeVote_test : public beast::unit_test::suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1)); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1))); accum.apply(*ledger); } @@ -534,7 +674,8 @@ class FeeVote_test : public beast::unit_test::suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2)); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2))); accum.apply(*ledger); } @@ -570,7 +711,7 @@ class FeeVote_test : public beast::unit_test::suite // The transaction should still succeed as long as other fields are // valid // The ledger sequence field is only used for informational purposes - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); } void @@ -596,7 +737,8 @@ class FeeVote_test : public beast::unit_test::suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1)); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1))); accum.apply(*ledger); } @@ -613,7 +755,8 @@ class FeeVote_test : public beast::unit_test::suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2)); + BEAST_EXPECT( + isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2))); accum.apply(*ledger); } @@ -649,7 +792,8 @@ class FeeVote_test : public beast::unit_test::suite }); OpenView accum(ledger.get()); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + BEAST_EXPECT( + !isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); } void @@ -836,6 +980,128 @@ class FeeVote_test : public beast::unit_test::suite XRPAmount{setup.owner_reserve}); } + void + testDoVotingSmartEscrow() + { + testcase("doVoting with Smart Escrow"); + + using namespace jtx; + + FeeSetup setup; + setup.reference_fee = 42; + setup.account_reserve = 1234567; + setup.owner_reserve = 7654321; + setup.extension_compute_limit = 100; + setup.extension_size_limit = 200; + setup.gas_price = 300; + + Env env( + *this, testable_amendments() | featureXRPFees | featureSmartEscrow); + + // establish what the current fees are + BEAST_EXPECT( + env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE}); + BEAST_EXPECT(env.current()->fees().reserve == XRPAmount{200'000'000}); + BEAST_EXPECT(env.current()->fees().increment == XRPAmount{50'000'000}); + BEAST_EXPECT(env.current()->fees().extensionComputeLimit == 0); + BEAST_EXPECT(env.current()->fees().extensionSizeLimit == 0); + BEAST_EXPECT(env.current()->fees().gasPrice == 0); + + auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote")); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // doVoting requires a flag ledger (every 256th ledger) + // We need to create a ledger at sequence 256 to make it a flag + // ledger + for (int i = 0; i < 256 - 1; ++i) + { + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + } + BEAST_EXPECT(ledger->isFlagLedger()); + + // Create some mock validations with fee votes + std::vector> validations; + + for (int i = 0; i < 5; i++) + { + auto sec = randomSecretKey(); + auto pub = derivePublicKey(KeyType::secp256k1, sec); + + auto val = std::make_shared( + env.app().timeKeeper().now(), + pub, + sec, + calcNodeID(pub), + [&](STValidation& v) { + v.setFieldU32(sfLedgerSequence, ledger->seq()); + // Vote for different fees than current + v.setFieldAmount( + sfBaseFeeDrops, XRPAmount{setup.reference_fee}); + v.setFieldAmount( + sfReserveBaseDrops, XRPAmount{setup.account_reserve}); + v.setFieldAmount( + sfReserveIncrementDrops, + XRPAmount{setup.owner_reserve}); + v.setFieldU32( + sfExtensionComputeLimit, setup.extension_compute_limit); + v.setFieldU32( + sfExtensionSizeLimit, setup.extension_size_limit); + v.setFieldU32(sfGasPrice, setup.gas_price); + }); + if (i % 2) + val->setTrusted(); + validations.push_back(val); + } + + auto txSet = std::make_shared( + SHAMapType::TRANSACTION, env.app().getNodeFamily()); + + // This should not throw since we have a flag ledger + feeVote->doVoting(ledger, validations, txSet); + + auto const txs = getTxs(txSet); + BEAST_EXPECT(txs.size() == 1); + auto const& feeTx = txs[0]; + + BEAST_EXPECT(feeTx.getTxnType() == ttFEE); + + BEAST_EXPECT(feeTx.getAccountID(sfAccount) == AccountID()); + BEAST_EXPECT(feeTx.getFieldU32(sfLedgerSequence) == ledger->seq() + 1); + + BEAST_EXPECT(feeTx.isFieldPresent(sfBaseFeeDrops)); + BEAST_EXPECT(feeTx.isFieldPresent(sfReserveBaseDrops)); + BEAST_EXPECT(feeTx.isFieldPresent(sfReserveIncrementDrops)); + + // The legacy fields should NOT be present + BEAST_EXPECT(!feeTx.isFieldPresent(sfBaseFee)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveBase)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveIncrement)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReferenceFeeUnits)); + + // Check the values + BEAST_EXPECT( + feeTx.getFieldAmount(sfBaseFeeDrops) == + XRPAmount{setup.reference_fee}); + BEAST_EXPECT( + feeTx.getFieldAmount(sfReserveBaseDrops) == + XRPAmount{setup.account_reserve}); + BEAST_EXPECT( + feeTx.getFieldAmount(sfReserveIncrementDrops) == + XRPAmount{setup.owner_reserve}); + BEAST_EXPECT( + feeTx.getFieldU32(sfExtensionComputeLimit) == + setup.extension_compute_limit); + BEAST_EXPECT( + feeTx.getFieldU32(sfExtensionSizeLimit) == + setup.extension_size_limit); + BEAST_EXPECT(feeTx.getFieldU32(sfGasPrice) == setup.gas_price); + } + void run() override { @@ -849,6 +1115,7 @@ class FeeVote_test : public beast::unit_test::suite testSingleInvalidTransaction(); testDoValidation(); testDoVoting(); + testDoVotingSmartEscrow(); } };