Files
clio/src/rpc/handlers/VaultInfo.cpp
2026-09-23 13:29:57 +01:00

152 lines
4.9 KiB
C++

#include "rpc/handlers/VaultInfo.hpp"
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/jss.h>
#include <expected>
#include <memory>
#include <optional>
#include <utility>
namespace rpc {
namespace {
/**
* @brief Ensures that the input contains either a `vaultID` alone, or both `owner` and
* `tnxSequence`. Any other combination is considered malformed.
*
* @param input The input object containing optional fields for the vault request.
* @return Returns true if the input is valid, false otherwise.
*/
bool
validate(VaultInfoHandler::Input const& input)
{
bool const hasVaultId = input.vaultID.has_value();
bool const hasOwner = input.owner.has_value();
bool const hasSeq = input.tnxSequence.has_value();
// Only valid combinations: (vaultID) or (owner + seq)
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
return (hasVaultId && !hasOwner && !hasSeq) || (!hasVaultId && hasOwner && hasSeq);
}
} // namespace
VaultInfoHandler::VaultInfoHandler(std::shared_ptr<BackendInterface> sharedPtrBackend)
: sharedPtrBackend_{std::move(sharedPtrBackend)}
{
}
VaultInfoHandler::Result
VaultInfoHandler::process(VaultInfoHandler::Input const& input, Context const& ctx) const
{
if (not validate(input))
return Error{ClioError::RpcMalformedRequest};
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "VaultInfo's ledger range must be available");
auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier(
*sharedPtrBackend_,
ctx.yield,
input.ledger,
range->maxSequence // NOLINT(bugprone-unchecked-optional-access)
);
if (not expectedLgrInfo.has_value())
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
// Extract the vault keylet based on input
auto const vaultKeylet = [&]() -> std::expected<xrpl::Keylet, Status> {
if (input.owner && input.tnxSequence) {
auto const& accountID = *input.owner;
// checks that account exists
{
auto const accountKeylet = xrpl::keylet::account(accountID);
auto const accountLedgerObject =
sharedPtrBackend_->fetchLedgerObject(accountKeylet.key, lgrInfo.seq, ctx.yield);
if (!accountLedgerObject)
return std::unexpected{Status{RippledError::RpcEntryNotFound}};
}
return xrpl::keylet::vault(accountID, xrpl::SeqProxy::rawSequence(*input.tnxSequence));
}
return xrpl::keylet::vault(*input.vaultID);
}();
if (not vaultKeylet.has_value())
return Error{vaultKeylet.error()};
// Fetch the vault object and it's associated issuance ID
auto const vaultLedgerObject =
sharedPtrBackend_->fetchLedgerObject(vaultKeylet->key, lgrInfo.seq, ctx.yield);
if (not vaultLedgerObject)
return Error{Status{RippledError::RpcEntryNotFound, "vault object not found."}};
xrpl::STLedgerEntry const vaultSle{
xrpl::SerialIter{vaultLedgerObject->data(), vaultLedgerObject->size()}, vaultKeylet->key
};
auto const issuanceKeylet = xrpl::keylet::mptokenIssuance(vaultSle[xrpl::sfShareMPTID]).key;
auto const issuanceObject =
sharedPtrBackend_->fetchLedgerObject(issuanceKeylet, lgrInfo.seq, ctx.yield);
if (not issuanceObject)
return Error{Status{RippledError::RpcEntryNotFound, "issuance object not found."}};
xrpl::STLedgerEntry const issuanceSle{
xrpl::SerialIter{issuanceObject->data(), issuanceObject->size()}, issuanceKeylet
};
// put issuance object into "shares" field of vault object
// follows same logic as rippled:
// https://github.com/XRPLF/rippled/pull/5224/files#diff-6cb544622c7942261f097d628f61f1c1fcf34a1bcfd954aedbada4238fc28f69R107
Output response;
response.vault = toBoostJson(vaultSle.getJson(xrpl::JsonOptions::Values::None));
response.vault.as_object()[JS(shares)] =
toBoostJson(issuanceSle.getJson(xrpl::JsonOptions::Values::None));
response.ledgerIndex = lgrInfo.seq;
return response;
}
void
tag_invoke(
boost::json::value_from_tag,
boost::json::value& jv,
VaultInfoHandler::Output const& output
)
{
jv = boost::json::object{
{JS(ledger_index), output.ledgerIndex},
{JS(validated), output.validated},
{JS(vault), output.vault}
};
}
} // namespace rpc