diff --git a/hook/sfcodes.h b/hook/sfcodes.h index d983fd1a6..0fc6e10b8 100644 --- a/hook/sfcodes.h +++ b/hook/sfcodes.h @@ -109,6 +109,7 @@ #define sfMPTAmount ((3U << 16U) + 26U) #define sfIssuerNode ((3U << 16U) + 27U) #define sfSubjectNode ((3U << 16U) + 28U) +#define sfHookGasPrice ((3U << 16U) + 96U) #define sfTouchCount ((3U << 16U) + 97U) #define sfAccountIndex ((3U << 16U) + 98U) #define sfAccountCount ((3U << 16U) + 99U) diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 4393f1a1d..7c07fe65b 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -34,6 +34,8 @@ struct Fees XRPAmount base{0}; // Reference tx cost (drops) XRPAmount reserve{0}; // Reserve base (drops) XRPAmount increment{0}; // Reserve increment (drops) + std::uint64_t hookGasPrice{ + 0}; // Gas price for gas-type hooks (micro-drops per gas unit) explicit Fees() = default; Fees(Fees const&) = default; diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index cb9efb7bf..347b219cb 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -416,6 +416,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({ {sfBaseFeeDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL}, + {sfHookGasPrice, soeOPTIONAL}, {sfXahauActivationLgrSeq, soeOPTIONAL}, {sfAccountCount, soeOPTIONAL}, {sfNetworkID, soeOPTIONAL}, diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 7281323b6..d8bde3b87 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -157,6 +157,7 @@ TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::sMD_BaseTen|SFie TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default) TYPED_SFIELD(sfIssuerNode, UINT64, 27) TYPED_SFIELD(sfSubjectNode, UINT64, 28) +TYPED_SFIELD(sfHookGasPrice, UINT64, 96) TYPED_SFIELD(sfTouchCount, UINT64, 97) TYPED_SFIELD(sfAccountIndex, UINT64, 98) TYPED_SFIELD(sfAccountCount, UINT64, 99) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index baed75230..d453236f2 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -583,6 +583,7 @@ TRANSACTION(ttFEE, 101, SetFee, ({ {sfBaseFeeDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL}, + {sfHookGasPrice, soeOPTIONAL}, })) /** This system-generated transaction type is used to update the network's negative UNL diff --git a/src/libxrpl/protocol/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp index 16f0c2887..29df84e96 100644 --- a/src/libxrpl/protocol/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -66,6 +66,8 @@ STValidation::validationFormat() {sfBaseFeeDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL}, + // featureHookGas + {sfHookGasPrice, soeOPTIONAL}, }; // clang-format on diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 289ce2a71..de36a3676 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -29,6 +29,7 @@ class FeeVote_test : public beast::unit_test::suite void testSetup() { + testcase("setup"); FeeSetup const defaultSetup; { // defaults @@ -37,36 +38,42 @@ class FeeVote_test : public beast::unit_test::suite BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); + BEAST_EXPECT(setup.hook_gas_price == defaultSetup.hook_gas_price); } { Section config; config.append( {"reference_fee = 50", "account_reserve = 1234567", - "owner_reserve = 1234"}); + "owner_reserve = 1234", + "hook_gas_price = 2000000"}); auto setup = setup_FeeVote(config); BEAST_EXPECT(setup.reference_fee == 50); BEAST_EXPECT(setup.account_reserve == 1234567); BEAST_EXPECT(setup.owner_reserve == 1234); + BEAST_EXPECT(setup.hook_gas_price == 2'000'000); } { Section config; config.append( {"reference_fee = blah", "account_reserve = yada", - "owner_reserve = foo"}); + "owner_reserve = foo", + "hook_gas_price =invalid"}); // Illegal values are ignored, and the defaults left unchanged auto setup = setup_FeeVote(config); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); + BEAST_EXPECT(setup.hook_gas_price == defaultSetup.hook_gas_price); } { Section config; config.append( {"reference_fee = -50", "account_reserve = -1234567", - "owner_reserve = -1234"}); + "owner_reserve = -1234", + "hook_gas_price = -2000000"}); // Illegal values are ignored, and the defaults left unchanged auto setup = setup_FeeVote(config); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); @@ -74,22 +81,30 @@ class FeeVote_test : public beast::unit_test::suite setup.account_reserve == static_cast(-1234567)); BEAST_EXPECT( setup.owner_reserve == static_cast(-1234)); + BEAST_EXPECT( + setup.hook_gas_price == static_cast(-2000000)); } { - const auto big64 = std::to_string( + const auto big64xrp = std::to_string( static_cast( std::numeric_limits::max()) + 1); + const auto big64 = std::to_string( + static_cast( + std::numeric_limits::max()) + + 1); Section config; config.append( - {"reference_fee = " + big64, - "account_reserve = " + big64, - "owner_reserve = " + big64}); + {"reference_fee = " + big64xrp, + "account_reserve = " + big64xrp, + "owner_reserve = " + big64xrp, + "hook_gas_price =" + big64}); // Illegal values are ignored, and the defaults left unchanged auto setup = setup_FeeVote(config); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); + BEAST_EXPECT(setup.hook_gas_price == defaultSetup.hook_gas_price); } } diff --git a/src/test/app/HookGasPrice_test.cpp b/src/test/app/HookGasPrice_test.cpp new file mode 100644 index 000000000..d950f06d2 --- /dev/null +++ b/src/test/app/HookGasPrice_test.cpp @@ -0,0 +1,260 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 XRPL Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class HookGasPrice_test : public beast::unit_test::suite +{ + // Advance to flag ledger so fee voting sets sfHookGasPrice + void + advanceToFlagLedger(jtx::Env& env) + { + auto const seq = env.current()->info().seq; + for (auto i = seq; i <= 256; ++i) + env.close(); + env.close(); + } + + void + testHookGasPriceGenesis(FeatureBitset features) + { + testcase("GasPrice genesis"); + using namespace jtx; + + // Test Env passes empty amendments to genesis, so sfHookGasPrice + // is NOT set in the genesis FeeSettings SLE. + { + Env env{*this, features}; + auto const sle = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice)); + } + + // Without featureHookGas - also no sfHookGasPrice + { + Env env{*this, features - featureHookGas}; + auto const sle = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice)); + } + } + + void + testHookGasPriceFeeVoting(FeatureBitset features) + { + testcase("GasPrice fee voting"); + using namespace jtx; + + Env env{*this, features}; + env.fund(XRP(10000), Account{"alice"}); + env.close(); + + // Before flag ledger: sfHookGasPrice not in SLE + { + auto const sle = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice)); + } + + // Advance to flag ledger to trigger fee voting + advanceToFlagLedger(env); + + // After fee voting with featureHookGas enabled: + // sfHookGasPrice should be set in SLE via ttFEE pseudo-transaction + { + auto const sle = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->isFieldPresent(sfHookGasPrice)); + if (sle->isFieldPresent(sfHookGasPrice)) + BEAST_EXPECT(sle->getFieldU64(sfHookGasPrice) == 1'000'000); + } + BEAST_EXPECT( + env.current()->fees().hookGasPrice == XRPAmount{1'000'000}); + } + + void + testHookGasPriceVotingUpdate(FeatureBitset features) + { + testcase("GasPrice voting update"); + using namespace jtx; + + // Create env with custom gas_price = 2,000,000 via voting config + auto cfg = envconfig(); + auto& votingSection = cfg->section("voting"); + votingSection.set("hook_gas_price", "2000000"); + // A validation_seed is required for fee voting to work + cfg->section("validation_seed").legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); + + Env env{*this, std::move(cfg), features}; + env.fund(XRP(10000), Account{"alice"}); + env.close(); + + // Before flag ledger: sfHookGasPrice not in SLE + { + auto const sle = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice)); + } + + // Advance to flag ledger to trigger fee voting + advanceToFlagLedger(env); + + // After voting: sfHookGasPrice should be updated to 2,000,000 + { + auto const sle = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->isFieldPresent(sfHookGasPrice)); + if (sle->isFieldPresent(sfHookGasPrice)) + BEAST_EXPECT(sle->getFieldU64(sfHookGasPrice) == 2'000'000); + } + } + + void + testHookGasPriceFeeCalculation(FeatureBitset features) + { + testcase("GasPrice fee calculation"); + using namespace jtx; + + { + // With default gasPrice = 1,000,000: + auto cfg = envconfig(); + auto& votingSection = cfg->section("voting"); + votingSection.set("hook_gas_price", "1000000"); + // A validation_seed is required for fee voting to work + cfg->section("validation_seed") + .legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); + + Env env{*this, std::move(cfg), features}; + env.fund(XRP(10000), Account{"alice"}); + env.close(); + + // Advance to flag ledger so gasPrice is set via fee voting + advanceToFlagLedger(env); + + auto const& fees = env.current()->fees(); + BEAST_EXPECT(fees.hookGasPrice == XRPAmount{1'000'000}); + + // Verify the gas price calculation formula: + // fee = gasCount * gasPrice / 1,000,000 + + // With default gasPrice = 1,000,000: + // 1,000,000 gas => 1,000,000 * 1,000,000 / 1,000,000 = 1,000,000 + // 500,000 gas => 500,000 drops + // 0 gas => 0 drops + for (auto gasCount : {1'000'000, 500'000, 0}) + { + auto gasFee = Transactor::calculateHookGas(gasCount, fees); + BEAST_EXPECT(gasFee == XRPAmount{gasCount}); + } + } + + { + // With gasPrice = 100,000: + auto cfg = envconfig(); + auto& votingSection = cfg->section("voting"); + votingSection.set("hook_gas_price", "100000"); + // A validation_seed is required for fee voting to work + cfg->section("validation_seed") + .legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); + + Env env{*this, std::move(cfg), features}; + env.fund(XRP(10000), Account{"alice"}); + env.close(); + + // Advance to flag ledger so gasPrice is set via fee voting + advanceToFlagLedger(env); + + auto const& fees = env.current()->fees(); + BEAST_EXPECT(fees.hookGasPrice == XRPAmount{100'000}); + + // Verify the gas price calculation formula: + // fee = gasCount * gasPrice / 1,000,000 + + // With default gasPrice = 1,000,000: + // 1,000,000 gas => 1,000,000 * 100,000 / 1,000,000 = 100,000 + // 500,000 gas => 50,000 drops + // 0 gas => 0 drops + for (auto gasCount : {1'000'000, 500'000, 0}) + { + auto gasFee = Transactor::calculateHookGas(gasCount, fees); + BEAST_EXPECT(gasFee == XRPAmount{gasCount / 10}); + } + } + } + + void + testHookGasPriceDisabled(FeatureBitset features) + { + testcase("GasPrice disabled"); + using namespace jtx; + + Env env{*this, features - featureHookGas}; + env.fund(XRP(10000), Account{"alice"}); + env.close(); + + // sfHookGasPrice not in SLE when amendment disabled + auto const sle = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice)); + + // Advance past flag ledger - sfHookGasPrice should still not be set + advanceToFlagLedger(env); + + { + auto const sle2 = env.le(keylet::fees()); + if (!BEAST_EXPECT(sle2)) + return; + BEAST_EXPECT(!sle2->isFieldPresent(sfHookGasPrice)); + } + } + + void + run() override + { + using namespace jtx; + auto const sa = supported_amendments(); + testHookGasPriceGenesis(sa); + testHookGasPriceFeeVoting(sa); + testHookGasPriceVotingUpdate(sa); + testHookGasPriceFeeCalculation(sa); + testHookGasPriceDisabled(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(HookGasPrice, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/xrpld/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp index 10d1c4436..54b48305c 100644 --- a/src/xrpld/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -244,6 +244,12 @@ Ledger::Ledger( sle->at(sfReserveIncrement) = *f; sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED; } + if (std::find(amendments.begin(), amendments.end(), featureHookGas) != + amendments.end()) + { + if (auto const f = config.FEES.hook_gas_price) + sle->at(sfHookGasPrice) = f; + } rawInsert(sle); } @@ -695,12 +701,22 @@ Ledger::setup() assign(fees_.increment, reserveIncrementXRP); newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP; } + // Read GasPrice + bool hookFees = false; + if (auto const gp = sle->at(~sfHookGasPrice)) + { + fees_.hookGasPrice = *gp; + hookFees = true; + } if (oldFees && newFees) // Should be all of one or the other, but not both ret = false; if (!rules_.enabled(featureXRPFees) && newFees) // Can't populate the new fees before the amendment is enabled ret = false; + if (!rules_.enabled(featureHookGas) && hookFees) + // Can't populate hook gas price before the amendment is enabled + ret = false; } } catch (SHAMapMissingNode const&) @@ -720,7 +736,8 @@ void Ledger::defaultFees(Config const& config) { XRPL_ASSERT( - fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0, + fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 && + fees_.hookGasPrice == 0, "ripple::Ledger::defaultFees : zero fees"); if (fees_.base == 0) fees_.base = config.FEES.reference_fee; @@ -728,6 +745,8 @@ Ledger::defaultFees(Config const& config) fees_.reserve = config.FEES.account_reserve; if (fees_.increment == 0) fees_.increment = config.FEES.owner_reserve; + if (fees_.hookGasPrice == 0) + fees_.hookGasPrice = config.FEES.hook_gas_price; } std::shared_ptr diff --git a/src/xrpld/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp index 95160f398..e57ee49b9 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -29,10 +29,10 @@ namespace ripple { namespace detail { +template class VotableValue { private: - using value_type = XRPAmount; value_type const current_; // The current setting value_type const target_; // The setting we want std::map voteMap_; @@ -67,8 +67,9 @@ public: getVotes() const; }; -auto -VotableValue::getVotes() const -> std::pair +template +std::pair +VotableValue::getVotes() const { value_type ourVote = current_; int weight = 0; @@ -191,6 +192,18 @@ FeeVoteImpl::doValidation( "reserve increment", sfReserveIncrement); } + + // GasPrice voting (always uses UINT64, independent of featureXRPFees) + if (rules.enabled(featureHookGas)) + { + if (lastFees.hookGasPrice != target_.hook_gas_price) + { + JLOG(journal_.info()) + << "Voting for hook gas price of " << target_.hook_gas_price; + if (auto const f = target_.hook_gas_price) + v[sfHookGasPrice] = f; + } + } } void @@ -213,11 +226,14 @@ FeeVoteImpl::doVoting( detail::VotableValue incReserveVote( lastClosedLedger->fees().increment, target_.owner_reserve); + detail::VotableValue hookGasPriceVote( + lastClosedLedger->fees().hookGasPrice, target_.hook_gas_price); + auto const& rules = lastClosedLedger->rules(); if (rules.enabled(featureXRPFees)) { auto doVote = [](std::shared_ptr const& val, - detail::VotableValue& value, + detail::VotableValue& value, SF_AMOUNT const& xrpField) { if (auto const field = ~val->at(~xrpField); field && field->native()) @@ -246,7 +262,7 @@ FeeVoteImpl::doVoting( else { auto doVote = [](std::shared_ptr const& val, - detail::VotableValue& value, + detail::VotableValue& value, auto const& valueField) { if (auto const field = val->at(~valueField)) { @@ -278,6 +294,33 @@ FeeVoteImpl::doVoting( } } + // GasPrice voting (UINT64 field, independent of featureXRPFees) + if (rules.enabled(featureHookGas)) + { + auto doVote = [](std::shared_ptr const& val, + detail::VotableValue& value, + SF_UINT64 const& xrpField) { + if (auto const field = val->at(~xrpField)) + { + auto const vote = *field; + if (vote <= std::numeric_limits::max()) + value.addVote(vote); + else + value.noVote(); + } + else + { + value.noVote(); + } + }; + for (auto const& val : set) + { + if (!val->isTrusted()) + continue; + doVote(val, hookGasPriceVote, sfHookGasPrice); + } + } + // choose our positions // TODO: Use structured binding once LLVM 16 is the minimum supported // version. See also: https://github.com/llvm/llvm-project/issues/48582 @@ -285,11 +328,13 @@ FeeVoteImpl::doVoting( auto const baseFee = baseFeeVote.getVotes(); auto const baseReserve = baseReserveVote.getVotes(); auto const incReserve = incReserveVote.getVotes(); + auto const hookGasPrice = hookGasPriceVote.getVotes(); auto const seq = lastClosedLedger->info().seq + 1; // add transactions to our position - if (baseFee.second || baseReserve.second || incReserve.second) + if (baseFee.second || baseReserve.second || incReserve.second || + (rules.enabled(featureHookGas) && hookGasPrice.second)) { JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee.first << "/" @@ -317,6 +362,10 @@ FeeVoteImpl::doVoting( incReserveVote.current()); obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED; } + if (rules.enabled(featureHookGas)) + { + obj[sfHookGasPrice] = hookGasPrice.first; + } }); uint256 txID = feeTx.getTransactionID(); diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index 0fec360b4..9a8ab4828 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -152,6 +152,18 @@ Change::preclaim(PreclaimContext const& ctx) ctx.tx.isFieldPresent(sfReserveIncrementDrops)) return temDISABLED; } + // sfHookGasPrice: required when featureHookGas is enabled, + // forbidden when disabled + if (ctx.view.rules().enabled(featureHookGas)) + { + if (!ctx.tx.isFieldPresent(sfHookGasPrice)) + return temMALFORMED; + } + else + { + if (ctx.tx.isFieldPresent(sfHookGasPrice)) + return temDISABLED; + } return tesSUCCESS; case ttAMENDMENT: case ttUNL_MODIFY: @@ -1022,6 +1034,8 @@ Change::applyFee() set(feeObject, ctx_.tx, sfBaseFeeDrops); set(feeObject, ctx_.tx, sfReserveBaseDrops); set(feeObject, ctx_.tx, sfReserveIncrementDrops); + if (ctx_.tx.isFieldPresent(sfHookGasPrice)) + set(feeObject, ctx_.tx, sfHookGasPrice); // Ensure the old fields are removed feeObject->makeFieldAbsent(sfBaseFee); feeObject->makeFieldAbsent(sfReferenceFeeUnits); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index d31c2fe77..aa5e0cf10 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -239,10 +239,16 @@ Transactor::Transactor(ApplyContext& ctx) { } +static constexpr uint64_t GAS_PRICE_MICRO_DROPS = 1'000'000; + XRPAmount -calculateHookGas(uint32_t gas) +Transactor::calculateHookGas(uint32_t gasCount, Fees const& fees) { - return XRPAmount{gas}; + uint64_t const gasPrice = + fees.hookGasPrice > 0 ? fees.hookGasPrice : GAS_PRICE_MICRO_DROPS; + // TODO: overflow check + return XRPAmount{static_cast( + (static_cast(gasCount) * gasPrice) / GAS_PRICE_MICRO_DROPS)}; } // RH NOTE: this only computes one chain at a time, so if there is a receiving @@ -324,7 +330,8 @@ Transactor::calculateHookChainFee( auto const weakFee = hookObj.isFieldPresent(sfHookWeakGas) ? hookObj.getFieldU32(sfHookWeakGas) : hookDef->getFieldU32(sfHookWeakGas); - XRPAmount const toAdd = calculateHookGas(weakFee); + XRPAmount const toAdd = + calculateHookGas(weakFee, view.fees()); if (fee + toAdd < fee) fee = XRPAmount{INITIAL_XRP.drops()}; else @@ -440,7 +447,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) if (callbackGas > 0) { XRPAmount const toAdd = - calculateHookGas(callbackGas); + calculateHookGas(callbackGas, view.fees()); if (hookExecutionFee + toAdd < hookExecutionFee) hookExecutionFee = XRPAmount{INITIAL_XRP.drops()}; @@ -474,7 +481,8 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) if (view.rules().enabled(featureHookGas) && tx.isFieldPresent(sfHookGas)) - hookExecutionFee += calculateHookGas(tx.getFieldU32(sfHookGas)); + hookExecutionFee += + calculateHookGas(tx.getFieldU32(sfHookGas), view.fees()); } XRPAmount accumulator = baseFee; diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index 3f0386d24..a12db2bc7 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -174,6 +174,9 @@ public: // Hooks + static XRPAmount + calculateHookGas(uint32_t gasCount, Fees const& fees); + static XRPAmount calculateHookChainFee( ReadView const& view, diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index f9573bab6..6211628de 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -82,6 +82,9 @@ struct FeeSetup /** The per-owned item reserve requirement in drops. */ XRPAmount owner_reserve{2 * DROPS_PER_XRP}; + /** The gas price in micro-drops per gas unit. */ + std::uint64_t hook_gas_price{1'000'000}; + /* (Remember to update the example cfg files when changing any of these * values.) */ }; diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index 34ae49c35..19448d26a 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1143,6 +1144,11 @@ setup_FeeVote(Section const& section) if (set(temp, "owner_reserve", section)) setup.owner_reserve = temp; } + { + std::int64_t temp; + if (set(temp, "hook_gas_price", section)) + setup.hook_gas_price = temp; + } return setup; } } // namespace ripple