Skip to content

Commit

Permalink
initial version of feature service fee, uncompiled untested
Browse files Browse the repository at this point in the history
  • Loading branch information
RichardAH committed Jan 26, 2025
1 parent d17f715 commit 6d97e3d
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 1 deletion.
139 changes: 139 additions & 0 deletions src/ripple/app/tx/impl/Transactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2039,6 +2039,145 @@ Transactor::operator()()
if (ctx_.size() > oversizeMetaDataCap)
result = tecOVERSIZE;
}

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 logic 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 ability to pay the fee
// and therefore enforcement logic is unnecessary chain-side.


STObject const& obj = const_cast<ripple::STTx&>(ctx_.tx)
.getField(sfServiceFee)
.downcast<STObject>();

// 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;
}

// Check if the destination account exists, if it doesn't just
// fail. Service fee cannot be used to create accounts.
auto const src = ctx_.tx.getAccountID(sfAccount);
auto const dst = obj.getAccountID(sfDestination);

auto const amt = obj.getFieldAmount(sfAmount);

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;
}

auto const& sleDst = view().read(keylet::account(dst));
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;
}

if (!sleDst)
{
JLOG(j_.trace())
<< "service fee not applied because destination "
<< dst << " does not exist.";
break;
}

// Next check if the dest is able to receive the currency

if (isXRP(amt))
{
// accounts can always receive native currency,
// just 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; // done
}

// execution to here means the service fee is specified in
// an issued currency

// 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;
}

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)
{
Expand Down
3 changes: 2 additions & 1 deletion src/ripple/protocol/Feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/SField.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions src/ripple/protocol/impl/InnerObjectFormats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ InnerObjectFormats::InnerObjectFormats()
{sfDigest, soeOPTIONAL},
{sfFlags, soeOPTIONAL},
});

add(sfServiceFee.jsonName.c_str(),
sfServiceFee.getCode(),
{
{sfAmount, soeREQUIRED},
{sfDestination, soeREQUIRED},
});
}

InnerObjectFormats const&
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/SField.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/TxFormats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ TxFormats::TxFormats()
{sfNetworkID, soeOPTIONAL},
{sfHookParameters, soeOPTIONAL},
{sfOperationLimit, soeOPTIONAL},
{sfServiceFee, soeOPTIONAL},
};

add(jss::AccountSet,
Expand Down

0 comments on commit 6d97e3d

Please sign in to comment.