mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-18 09:38:03 +00:00
Compare commits
1 Commits
mvadari/re
...
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
|
#pragma once
|
||||||
|
|
||||||
#include <xrpl/tx/Transactor.h>
|
#include <xrpl/tx/Transactor.h>
|
||||||
|
#include <xrpl/tx/invariants/VaultInvariantData.h>
|
||||||
|
|
||||||
namespace xrpl {
|
namespace xrpl {
|
||||||
|
|
||||||
@@ -38,6 +39,9 @@ public:
|
|||||||
XRPAmount fee,
|
XRPAmount fee,
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
beast::Journal const& j) override;
|
beast::Journal const& j) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VaultInvariantData data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xrpl
|
} // namespace xrpl
|
||||||
|
|||||||
@@ -544,61 +544,13 @@ ValidVault::finalize(
|
|||||||
result &= [&]() {
|
result &= [&]() {
|
||||||
switch (txnType)
|
switch (txnType)
|
||||||
{
|
{
|
||||||
case ttVAULT_CREATE: {
|
case ttVAULT_CREATE:
|
||||||
bool result = true;
|
case ttLOAN_SET:
|
||||||
|
case ttLOAN_MANAGE:
|
||||||
if (!beforeVault_.empty())
|
case ttLOAN_PAY:
|
||||||
{
|
// Create-specific checks live in VaultCreate::finalizeInvariants.
|
||||||
JLOG(j.fatal()) //
|
// Loan checks are TBD.
|
||||||
<< "Invariant failed: create operation must not have "
|
return true;
|
||||||
"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_SET: {
|
case ttVAULT_SET: {
|
||||||
bool result = true;
|
bool result = true;
|
||||||
|
|
||||||
@@ -1042,13 +994,6 @@ ValidVault::finalize(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ttLOAN_SET:
|
|
||||||
case ttLOAN_MANAGE:
|
|
||||||
case ttLOAN_PAY: {
|
|
||||||
// TBD
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type");
|
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/tx/transactors/vault/VaultCreate.h>
|
||||||
|
|
||||||
|
#include <xrpl/basics/Log.h>
|
||||||
#include <xrpl/basics/Number.h>
|
#include <xrpl/basics/Number.h>
|
||||||
#include <xrpl/basics/base_uint.h>
|
#include <xrpl/basics/base_uint.h>
|
||||||
#include <xrpl/beast/utility/Zero.h>
|
#include <xrpl/beast/utility/Zero.h>
|
||||||
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
#include <xrpl/core/ServiceRegistry.h>
|
#include <xrpl/core/ServiceRegistry.h>
|
||||||
#include <xrpl/ledger/View.h>
|
#include <xrpl/ledger/View.h>
|
||||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||||
@@ -263,15 +265,107 @@ VaultCreate::doApply()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user