mirror of
https://github.com/XRPLF/clio.git
synced 2026-06-06 02:06:58 +00:00
226 lines
7.1 KiB
C++
226 lines
7.1 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of clio: https://github.com/XRPLF/clio
|
|
Copyright (c) 2023, the clio developers.
|
|
|
|
Permission to use, copy, modify, and distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include "rpc/handlers/NFTOffersCommon.hpp"
|
|
|
|
#include "rpc/Errors.hpp"
|
|
#include "rpc/JS.hpp"
|
|
#include "rpc/RPCHelpers.hpp"
|
|
#include "rpc/common/Types.hpp"
|
|
#include "util/Assert.hpp"
|
|
#include "util/JsonUtils.hpp"
|
|
|
|
#include <boost/asio/spawn.hpp>
|
|
#include <boost/json/conversion.hpp>
|
|
#include <boost/json/object.hpp>
|
|
#include <boost/json/value.hpp>
|
|
#include <boost/json/value_to.hpp>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/Keylet.h>
|
|
#include <xrpl/protocol/LedgerFormats.h>
|
|
#include <xrpl/protocol/LedgerHeader.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STBase.h>
|
|
#include <xrpl/protocol/STLedgerEntry.h>
|
|
#include <xrpl/protocol/Serializer.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace ripple;
|
|
using namespace ::rpc;
|
|
|
|
namespace ripple {
|
|
|
|
// TODO: move to some common serialization impl place
|
|
inline static void
|
|
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, SLE const& offer)
|
|
{
|
|
auto amount = ::toBoostJson(offer.getFieldAmount(sfAmount).getJson(JsonOptions::none));
|
|
|
|
boost::json::object obj = {
|
|
{JS(nft_offer_index), to_string(offer.key())},
|
|
{JS(flags), offer[sfFlags]},
|
|
{JS(owner), toBase58(offer.getAccountID(sfOwner))},
|
|
{JS(amount), std::move(amount)},
|
|
};
|
|
|
|
if (offer.isFieldPresent(sfDestination))
|
|
obj.insert_or_assign(JS(destination), toBase58(offer.getAccountID(sfDestination)));
|
|
|
|
if (offer.isFieldPresent(sfExpiration))
|
|
obj.insert_or_assign(JS(expiration), offer.getFieldU32(sfExpiration));
|
|
|
|
jv = std::move(obj);
|
|
}
|
|
|
|
} // namespace ripple
|
|
|
|
namespace rpc {
|
|
|
|
NFTOffersHandlerBase::Result
|
|
NFTOffersHandlerBase::iterateOfferDirectory(
|
|
Input input,
|
|
ripple::uint256 const& tokenID,
|
|
ripple::Keylet const& directory,
|
|
boost::asio::yield_context yield
|
|
) const
|
|
{
|
|
auto const range = sharedPtrBackend_->fetchLedgerRange();
|
|
ASSERT(range.has_value(), "NFTOffersCommon's ledger range must be available");
|
|
|
|
auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq(
|
|
*sharedPtrBackend_, yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
|
|
);
|
|
|
|
if (not expectedLgrInfo.has_value())
|
|
return Error{expectedLgrInfo.error()};
|
|
|
|
auto const& lgrInfo = expectedLgrInfo.value();
|
|
|
|
// TODO: just check for existence without pulling
|
|
if (not sharedPtrBackend_->fetchLedgerObject(directory.key, lgrInfo.seq, yield))
|
|
return Error{Status{RippledError::rpcOBJECT_NOT_FOUND, "notFound"}};
|
|
|
|
auto output = Output{.nftID = input.nftID, .offers = {}, .limit = {}, .marker = {}};
|
|
auto offers = std::vector<ripple::SLE>{};
|
|
auto reserve = input.limit;
|
|
auto cursor = uint256{};
|
|
auto startHint = uint64_t{0ul};
|
|
|
|
if (input.marker) {
|
|
cursor = uint256(input.marker->c_str());
|
|
|
|
// We have a start point. Use limit - 1 from the result and use the very last one for the
|
|
// resume.
|
|
auto const sle = [this, &cursor, &lgrInfo, yield]() -> std::shared_ptr<SLE const> {
|
|
auto const key = keylet::nftoffer(cursor).key;
|
|
|
|
if (auto const blob = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, yield);
|
|
blob)
|
|
return std::make_shared<SLE const>(SerialIter{blob->data(), blob->size()}, key);
|
|
|
|
return nullptr;
|
|
}();
|
|
|
|
if (!sle || sle->getFieldU16(ripple::sfLedgerEntryType) != ripple::ltNFTOKEN_OFFER ||
|
|
tokenID != sle->getFieldH256(ripple::sfNFTokenID)) {
|
|
return Error{Status{RippledError::rpcINVALID_PARAMS}};
|
|
}
|
|
|
|
startHint = sle->getFieldU64(ripple::sfNFTokenOfferNode);
|
|
output.offers.push_back(*sle);
|
|
offers.reserve(reserve);
|
|
} else {
|
|
// We have no start point, limit should be one higher than requested.
|
|
offers.reserve(++reserve);
|
|
}
|
|
|
|
auto result = traverseOwnedNodes(
|
|
*sharedPtrBackend_,
|
|
directory,
|
|
cursor,
|
|
startHint,
|
|
lgrInfo.seq,
|
|
reserve,
|
|
yield,
|
|
[&offers](ripple::SLE&& offer) {
|
|
if (offer.getType() == ripple::ltNFTOKEN_OFFER) {
|
|
offers.push_back(std::move(offer));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
if (!result.has_value())
|
|
return Error{result.error()};
|
|
|
|
if (offers.size() == reserve) {
|
|
output.limit = input.limit;
|
|
output.marker = to_string(offers.back().key());
|
|
offers.pop_back();
|
|
}
|
|
|
|
std::ranges::move(offers, std::back_inserter(output.offers));
|
|
|
|
return output;
|
|
}
|
|
|
|
void
|
|
tag_invoke(
|
|
boost::json::value_from_tag,
|
|
boost::json::value& jv,
|
|
NFTOffersHandlerBase::Output const& output
|
|
)
|
|
{
|
|
using boost::json::value_from;
|
|
|
|
auto object = boost::json::object{
|
|
{JS(nft_id), output.nftID},
|
|
{JS(validated), output.validated},
|
|
{JS(offers), value_from(output.offers)},
|
|
};
|
|
|
|
if (output.marker)
|
|
object[JS(marker)] = *(output.marker);
|
|
|
|
if (output.limit)
|
|
object[JS(limit)] = *(output.limit);
|
|
|
|
jv = std::move(object);
|
|
}
|
|
|
|
NFTOffersHandlerBase::Input
|
|
tag_invoke(boost::json::value_to_tag<NFTOffersHandlerBase::Input>, boost::json::value const& jv)
|
|
{
|
|
auto input = NFTOffersHandlerBase::Input{};
|
|
auto const& jsonObject = jv.as_object();
|
|
|
|
input.nftID = boost::json::value_to<std::string>(jsonObject.at(JS(nft_id)));
|
|
|
|
if (jsonObject.contains(JS(ledger_hash)))
|
|
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
|
|
|
if (jsonObject.contains(JS(ledger_index))) {
|
|
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
|
if (expectedLedgerIndex.has_value())
|
|
input.ledgerIndex = *expectedLedgerIndex;
|
|
}
|
|
|
|
if (jsonObject.contains(JS(marker)))
|
|
input.marker = boost::json::value_to<std::string>(jsonObject.at(JS(marker)));
|
|
|
|
if (jsonObject.contains(JS(limit)))
|
|
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
|
|
|
|
return input;
|
|
}
|
|
|
|
} // namespace rpc
|