mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-30 07:25:50 +00:00
initial version of feature service fee, uncompiled untested
This commit is contained in:
@@ -2040,32 +2040,167 @@ Transactor::operator()()
|
||||
result = tecOVERSIZE;
|
||||
}
|
||||
|
||||
if (applied)
|
||||
{
|
||||
// Transaction succeeded fully or (retries are not allowed and the
|
||||
// transaction could claim a fee)
|
||||
if (view().rules().enabled(featureServiceFee) && applied &&
|
||||
ctx_.tx.isFieldPresent(sfServiceFee))
|
||||
do
|
||||
{
|
||||
// Service fee is processed on a best-effort basis without affecting
|
||||
// tx application. The reason is that the client completely controls
|
||||
// the service fee that it submits with the user's txn, and
|
||||
// therefore is already completely aware of the user's capacity to
|
||||
// pay the fee and therefore enforcement logic is unnecessary
|
||||
// chain-side.
|
||||
|
||||
// The transactor and invariant checkers guarantee that this will
|
||||
// *never* trigger but if it, somehow, happens, don't allow a tx
|
||||
// that charges a negative fee.
|
||||
if (fee < beast::zero)
|
||||
Throw<std::logic_error>("fee charged is negative!");
|
||||
STObject const& obj = const_cast<ripple::STTx&>(ctx_.tx)
|
||||
.getField(sfServiceFee)
|
||||
.downcast<STObject>();
|
||||
|
||||
// Charge whatever fee they specified. The fee has already been
|
||||
// deducted from the balance of the account that issued the
|
||||
// transaction. We just need to account for it in the ledger
|
||||
// header.
|
||||
if (!view().open() && fee != beast::zero)
|
||||
ctx_.destroyXRP(fee);
|
||||
// This should be enforced by template but doesn't hurt to
|
||||
// defensively check it here.
|
||||
if (!obj.isFieldPresent(sfDestination) ||
|
||||
!obj.isFieldPresent(sfAmount) || obj.getCount() != 2)
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "service fee not applied - malformed inner object.";
|
||||
break;
|
||||
}
|
||||
|
||||
// Once we call apply, we will no longer be able to look at view()
|
||||
ctx_.apply(result);
|
||||
}
|
||||
auto const src = ctx_.tx.getAccountID(sfAccount);
|
||||
auto const dst = obj.getAccountID(sfDestination);
|
||||
|
||||
JLOG(j_.trace()) << (applied ? "applied" : "not applied")
|
||||
<< transToken(result);
|
||||
auto const amt = obj.getFieldAmount(sfAmount);
|
||||
|
||||
return {result, applied};
|
||||
}
|
||||
// sanity check fields
|
||||
if (src == dst)
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "skipping self service-fee on " << src << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
if (amt <= beast::zero)
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "skipping non-positive service-fee from " << src << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
// check if the source exists
|
||||
auto const& sleSrc = view().read(keylet::account(src));
|
||||
if (!sleSrc)
|
||||
{
|
||||
// this can happen if the account was just deleted
|
||||
JLOG(j_.trace()) << "service fee not applied because source "
|
||||
<< src << " does not exist.";
|
||||
break;
|
||||
}
|
||||
|
||||
// check if the destination exists
|
||||
// service fee cannot be used to create accounts.
|
||||
if (!view().exists(keylet::account(dst)))
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "service fee not applied because destination " << dst
|
||||
<< " does not exist.";
|
||||
break;
|
||||
}
|
||||
|
||||
if (isXRP(amt))
|
||||
{
|
||||
// check if there's enough left in the sender's account
|
||||
auto srcBal = sleSrc.getFieldAmount(sfBalance);
|
||||
|
||||
// service fee will only be delivered if the account
|
||||
// contains adequate balance to cover reserves, otherwise
|
||||
// it is disregarded
|
||||
auto after = srcBal - amt;
|
||||
if (after < view().fees().accountReserve(
|
||||
sleSrc->getFieldU32(sfOwnerCount)))
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "service fee not applied because source " << src
|
||||
<< " cannot pay it (native).";
|
||||
break;
|
||||
}
|
||||
|
||||
// action the transfer
|
||||
if (TER const ter{
|
||||
view().transferXRP(view(), src, dst, amt, j_))
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "service fee error transferring " << amt << " from "
|
||||
<< src << " to " << dst << " error: " << ter << ".";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// execution to here means issued currency service fee
|
||||
|
||||
// service fee cannot be used to create trustlines,
|
||||
// so a line must already exist and the currency must
|
||||
// be able to be xfer'd to it
|
||||
|
||||
auto const& sleLine = view().peek(keylet::line(dst, amt.getIssuer(), amt.getCurrency()));
|
||||
|
||||
if (!sleLine && amt.getIssuer() != dst)
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "service fee not applied because destination " << dst
|
||||
<< " has no trustline for currency: "
|
||||
<< amt.getCurrency()
|
||||
<< " issued by: " << amt.getIssuer() << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
// action the transfer
|
||||
{
|
||||
PaymentSandbox pv(&view());
|
||||
auto res = accountSend(pv, src, dst, amt, j_);
|
||||
|
||||
if (res == tesSUCCESS)
|
||||
{
|
||||
pv.apply(ctx_.rawView());
|
||||
break;
|
||||
}
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< "service fee not sent from " << src << " to " << dst
|
||||
<< " for " << amt.getCurrency() " issued by "
|
||||
<< amt.getIssuer() " because "
|
||||
<< "accountSend() failed with code " << res << ".";
|
||||
}
|
||||
}
|
||||
while (0)
|
||||
;
|
||||
|
||||
if (applied)
|
||||
{
|
||||
// Transaction succeeded fully or (retries are not allowed and
|
||||
// the transaction could claim a fee)
|
||||
|
||||
// The transactor and invariant checkers guarantee that this
|
||||
// will *never* trigger but if it, somehow, happens, don't allow
|
||||
// a tx that charges a negative fee.
|
||||
if (fee < beast::zero)
|
||||
Throw<std::logic_error>("fee charged is negative!");
|
||||
|
||||
// Charge whatever fee they specified. The fee has already been
|
||||
// deducted from the balance of the account that issued the
|
||||
// transaction. We just need to account for it in the ledger
|
||||
// header.
|
||||
if (!view().open() && fee != beast::zero)
|
||||
ctx_.destroyXRP(fee);
|
||||
|
||||
// Once we call apply, we will no longer be able to look at
|
||||
// view()
|
||||
ctx_.apply(result);
|
||||
}
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< (applied ? "applied" : "not applied") << transToken(result);
|
||||
|
||||
return {result, applied};
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 76;
|
||||
static constexpr std::size_t numFeatures = 77;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -364,6 +364,7 @@ extern uint256 const fix240911;
|
||||
extern uint256 const fixFloatDivide;
|
||||
extern uint256 const fixReduceImport;
|
||||
extern uint256 const fixXahauV3;
|
||||
extern uint256 const featureServiceFee;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -608,6 +608,7 @@ extern SField const sfMemos;
|
||||
extern SField const sfNFTokens;
|
||||
extern SField const sfHooks;
|
||||
extern SField const sfGenesisMint;
|
||||
extern SField const sfServiceFee;
|
||||
|
||||
// array of objects (uncommon)
|
||||
extern SField const sfMajorities;
|
||||
|
||||
@@ -470,6 +470,7 @@ REGISTER_FIX (fix240911, Supported::yes, VoteBehavior::De
|
||||
REGISTER_FIX (fixFloatDivide, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixReduceImport, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixXahauV3, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FEATURE(ServiceFee, Supported::yes, VoteBehavior::DefaultNo);
|
||||
|
||||
// The following amendments are obsolete, but must remain supported
|
||||
// because they could potentially get enabled.
|
||||
|
||||
@@ -157,6 +157,13 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfDigest, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL},
|
||||
});
|
||||
|
||||
add(sfServiceFee.jsonName.c_str(),
|
||||
sfServiceFee.getCode(),
|
||||
{
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -350,6 +350,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT,
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfServiceFee, "ServiceFee", OBJECT, 90);
|
||||
|
||||
// array of objects
|
||||
// ARRAY/1 is reserved for end of array
|
||||
|
||||
@@ -44,6 +44,7 @@ TxFormats::TxFormats()
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfOperationLimit, soeOPTIONAL},
|
||||
{sfServiceFee, soeOPTIONAL},
|
||||
};
|
||||
|
||||
add(jss::AccountSet,
|
||||
|
||||
Reference in New Issue
Block a user