mirror of
https://github.com/XRPLF/clio.git
synced 2026-09-29 08:28:06 +00:00
152 lines
4.9 KiB
C++
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
|