mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-10 04:06:59 +00:00
Compare commits
1 Commits
bthomee/se
...
tapanito/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0a54d1159 |
76
include/xrpl/tx/invariants/VaultInvariantData.h
Normal file
76
include/xrpl/tx/invariants/VaultInvariantData.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Collects vault and share-issuance snapshots from ledger entry visits.
|
||||
*
|
||||
* Used by per-transaction invariant checks (e.g. VaultCreate) that need
|
||||
* vault and MPTokenIssuance state without the full balance-delta tracking
|
||||
* that ValidVault maintains.
|
||||
*/
|
||||
class VaultInvariantData
|
||||
{
|
||||
public:
|
||||
struct Vault
|
||||
{
|
||||
uint256 key = beast::kZero;
|
||||
Asset asset;
|
||||
AccountID pseudoId;
|
||||
AccountID owner;
|
||||
uint192 shareMPTID = beast::kZero;
|
||||
Number assetsTotal = 0;
|
||||
Number assetsAvailable = 0;
|
||||
Number assetsMaximum = 0;
|
||||
Number lossUnrealized = 0;
|
||||
|
||||
static Vault
|
||||
make(SLE const&);
|
||||
};
|
||||
|
||||
struct Shares
|
||||
{
|
||||
MPTIssue share;
|
||||
std::uint64_t sharesTotal = 0;
|
||||
std::uint64_t sharesMaximum = 0;
|
||||
|
||||
static Shares
|
||||
make(SLE const&);
|
||||
};
|
||||
|
||||
void
|
||||
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after);
|
||||
|
||||
[[nodiscard]] std::vector<Vault> const&
|
||||
afterVaults() const
|
||||
{
|
||||
return afterVault_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<Vault> const&
|
||||
beforeVaults() const
|
||||
{
|
||||
return beforeVault_;
|
||||
}
|
||||
|
||||
/** Find shares in afterMPTs_ whose mptID matches. */
|
||||
[[nodiscard]] std::optional<Shares>
|
||||
findShares(uint192 const& mptID) const;
|
||||
|
||||
private:
|
||||
std::vector<Vault> afterVault_;
|
||||
std::vector<Vault> beforeVault_;
|
||||
std::vector<Shares> afterMPTs_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/invariants/VaultInvariantData.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -38,6 +39,9 @@ public:
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
|
||||
private:
|
||||
VaultInvariantData data_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -544,61 +544,13 @@ ValidVault::finalize(
|
||||
result &= [&]() {
|
||||
switch (txnType)
|
||||
{
|
||||
case ttVAULT_CREATE: {
|
||||
bool result = true;
|
||||
|
||||
if (!beforeVault_.empty())
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: create operation must not have "
|
||||
"updated a vault";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsAvailable != kZero || afterVault.assetsTotal != kZero ||
|
||||
afterVault.lossUnrealized != kZero || updatedShares->sharesTotal != 0)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: created vault must be empty";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.pseudoId != updatedShares->share.getIssuer())
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer and vault "
|
||||
"pseudo-account must be the same";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const sleSharesIssuer =
|
||||
view.read(keylet::account(updatedShares->share.getIssuer()));
|
||||
if (!sleSharesIssuer)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isPseudoAccount(sleSharesIssuer))
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must be a "
|
||||
"pseudo-account";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
|
||||
!vaultId || *vaultId != afterVault.key)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer pseudo-account "
|
||||
"must point back to the vault";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case ttVAULT_CREATE:
|
||||
case ttLOAN_SET:
|
||||
case ttLOAN_MANAGE:
|
||||
case ttLOAN_PAY:
|
||||
// Create-specific checks live in VaultCreate::finalizeInvariants.
|
||||
// Loan checks are TBD.
|
||||
return true;
|
||||
case ttVAULT_SET: {
|
||||
bool result = true;
|
||||
|
||||
@@ -1042,13 +994,6 @@ ValidVault::finalize(
|
||||
return result;
|
||||
}
|
||||
|
||||
case ttLOAN_SET:
|
||||
case ttLOAN_MANAGE:
|
||||
case ttLOAN_PAY: {
|
||||
// TBD
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type");
|
||||
|
||||
80
src/libxrpl/tx/invariants/VaultInvariantData.cpp
Normal file
80
src/libxrpl/tx/invariants/VaultInvariantData.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <xrpl/tx/invariants/VaultInvariantData.h>
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
VaultInvariantData::Vault
|
||||
VaultInvariantData::Vault::make(SLE const& from)
|
||||
{
|
||||
XRPL_ASSERT(from.getType() == ltVAULT, "VaultInvariantData::Vault::make : from Vault object");
|
||||
|
||||
Vault self;
|
||||
self.key = from.key();
|
||||
self.asset = from.at(sfAsset);
|
||||
self.pseudoId = from.getAccountID(sfAccount);
|
||||
self.owner = from.at(sfOwner);
|
||||
self.shareMPTID = from.getFieldH192(sfShareMPTID);
|
||||
self.assetsTotal = from.at(sfAssetsTotal);
|
||||
self.assetsAvailable = from.at(sfAssetsAvailable);
|
||||
self.assetsMaximum = from.at(sfAssetsMaximum);
|
||||
self.lossUnrealized = from.at(sfLossUnrealized);
|
||||
return self;
|
||||
}
|
||||
|
||||
VaultInvariantData::Shares
|
||||
VaultInvariantData::Shares::make(SLE const& from)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
from.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"VaultInvariantData::Shares::make : from MPTokenIssuance object");
|
||||
|
||||
Shares self;
|
||||
self.share = MPTIssue(makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
|
||||
self.sharesTotal = from.getFieldU64(sfOutstandingAmount);
|
||||
self.sharesMaximum = from[~sfMaximumAmount].value_or(kMaxMpTokenAmount);
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
VaultInvariantData::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
after != nullptr && (before != nullptr || !isDelete),
|
||||
"xrpl::VaultInvariantData::visitEntry : some object is available");
|
||||
|
||||
if (before && before->getType() == ltVAULT)
|
||||
beforeVault_.push_back(Vault::make(*before));
|
||||
|
||||
if (!isDelete && after)
|
||||
{
|
||||
switch (after->getType())
|
||||
{
|
||||
case ltVAULT:
|
||||
afterVault_.push_back(Vault::make(*after));
|
||||
break;
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
afterMPTs_.push_back(Shares::make(*after));
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<VaultInvariantData::Shares>
|
||||
VaultInvariantData::findShares(uint192 const& mptID) const
|
||||
{
|
||||
for (auto const& s : afterMPTs_)
|
||||
{
|
||||
if (s.share.getMptID() == mptID)
|
||||
return s;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <xrpl/tx/transactors/vault/VaultCreate.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
@@ -263,15 +265,107 @@ VaultCreate::doApply()
|
||||
}
|
||||
|
||||
void
|
||||
VaultCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
VaultCreate::visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
data_.visitEntry(isDelete, before, after);
|
||||
}
|
||||
|
||||
bool
|
||||
VaultCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
VaultCreate::finalizeInvariants(
|
||||
STTx const&,
|
||||
TER result,
|
||||
XRPAmount,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
bool const enforce = view.rules().enabled(featureSingleAssetVault);
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return true;
|
||||
|
||||
auto const& afterVaults = data_.afterVaults();
|
||||
if (afterVaults.empty())
|
||||
return true;
|
||||
|
||||
auto const& afterVault = afterVaults[0];
|
||||
bool checkResult = true;
|
||||
|
||||
if (!data_.beforeVaults().empty())
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: create operation must not have updated a vault";
|
||||
checkResult = false;
|
||||
}
|
||||
|
||||
// The MPTokenIssuance may not be in the modified set (e.g. only the vault
|
||||
// was touched in the test), so fall back to a view read if needed.
|
||||
auto const updatedShares = [&]() -> std::optional<VaultInvariantData::Shares> {
|
||||
if (auto found = data_.findShares(afterVault.shareMPTID))
|
||||
return found;
|
||||
auto const sleShares = view.read(keylet::mptIssuance(afterVault.shareMPTID));
|
||||
return sleShares ? std::optional<VaultInvariantData::Shares>(
|
||||
VaultInvariantData::Shares::make(*sleShares))
|
||||
: std::nullopt;
|
||||
}();
|
||||
|
||||
static constexpr Number kZero{};
|
||||
|
||||
if (afterVault.assetsAvailable != kZero || afterVault.assetsTotal != kZero ||
|
||||
afterVault.lossUnrealized != kZero || (updatedShares && updatedShares->sharesTotal != 0))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: created vault must be empty";
|
||||
checkResult = false;
|
||||
}
|
||||
|
||||
if (!updatedShares)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
|
||||
XRPL_ASSERT(enforce, "xrpl::VaultCreate::finalizeInvariants : vault has shares invariant");
|
||||
if (!checkResult)
|
||||
{
|
||||
XRPL_ASSERT(enforce, "xrpl::VaultCreate::finalizeInvariants : vault create invariants");
|
||||
}
|
||||
return !enforce;
|
||||
}
|
||||
|
||||
if (afterVault.pseudoId != updatedShares->share.getIssuer())
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: shares issuer and vault pseudo-account must be the same";
|
||||
checkResult = false;
|
||||
}
|
||||
|
||||
auto const sleSharesIssuer = view.read(keylet::account(updatedShares->share.getIssuer()));
|
||||
if (!sleSharesIssuer)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: shares issuer must exist";
|
||||
XRPL_ASSERT(
|
||||
enforce, "xrpl::VaultCreate::finalizeInvariants : shares issuer exists invariant");
|
||||
if (!checkResult)
|
||||
{
|
||||
XRPL_ASSERT(enforce, "xrpl::VaultCreate::finalizeInvariants : vault create invariants");
|
||||
}
|
||||
return !enforce;
|
||||
}
|
||||
|
||||
if (!isPseudoAccount(sleSharesIssuer))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: shares issuer must be a pseudo-account";
|
||||
checkResult = false;
|
||||
}
|
||||
|
||||
if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID]; !vaultId || *vaultId != afterVault.key)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: shares issuer pseudo-account must point back to the vault";
|
||||
checkResult = false;
|
||||
}
|
||||
|
||||
if (!checkResult)
|
||||
{
|
||||
XRPL_ASSERT(enforce, "xrpl::VaultCreate::finalizeInvariants : vault create invariants");
|
||||
return !enforce;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user