|
| 1 | +//------------------------------------------------------------------------------ |
| 2 | +/* |
| 3 | + This file is part of clio: https://github.com/XRPLF/clio |
| 4 | + Copyright (c) 2025, the clio developers. |
| 5 | +
|
| 6 | + Permission to use, copy, modify, and distribute this software for any |
| 7 | + purpose with or without fee is hereby granted, provided that the above |
| 8 | + copyright notice and this permission notice appear in all copies. |
| 9 | +
|
| 10 | + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 11 | + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 12 | + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 13 | + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 14 | + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 15 | + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 16 | + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 17 | +*/ |
| 18 | +//============================================================================== |
| 19 | + |
| 20 | +#include "rpc/handlers/AccountMPTokenIssuances.hpp" |
| 21 | + |
| 22 | +#include "rpc/Errors.hpp" |
| 23 | +#include "rpc/JS.hpp" |
| 24 | +#include "rpc/RPCHelpers.hpp" |
| 25 | +#include "rpc/common/Types.hpp" |
| 26 | +#include "util/Assert.hpp" |
| 27 | +#include "util/JsonUtils.hpp" |
| 28 | + |
| 29 | +#include <boost/json/conversion.hpp> |
| 30 | +#include <boost/json/object.hpp> |
| 31 | +#include <boost/json/value.hpp> |
| 32 | +#include <boost/json/value_to.hpp> |
| 33 | +#include <xrpl/basics/strHex.h> |
| 34 | +#include <xrpl/protocol/AccountID.h> |
| 35 | +#include <xrpl/protocol/Indexes.h> |
| 36 | +#include <xrpl/protocol/LedgerFormats.h> |
| 37 | +#include <xrpl/protocol/LedgerHeader.h> |
| 38 | +#include <xrpl/protocol/SField.h> |
| 39 | +#include <xrpl/protocol/STAmount.h> |
| 40 | +#include <xrpl/protocol/STLedgerEntry.h> |
| 41 | +#include <xrpl/protocol/UintTypes.h> |
| 42 | +#include <xrpl/protocol/jss.h> |
| 43 | + |
| 44 | +#include <cstdint> |
| 45 | +#include <optional> |
| 46 | +#include <string> |
| 47 | +#include <utility> |
| 48 | +#include <vector> |
| 49 | + |
| 50 | +namespace rpc { |
| 51 | + |
| 52 | +void |
| 53 | +AccountMPTokenIssuancesHandler::addMPTokenIssuance( |
| 54 | + std::vector<MPTokenIssuancesResponse>& issuances, |
| 55 | + ripple::SLE const& sle, |
| 56 | + ripple::AccountID const& account |
| 57 | +) |
| 58 | +{ |
| 59 | + MPTokenIssuancesResponse issuance; |
| 60 | + |
| 61 | + issuance.issuer = ripple::to_string(account); |
| 62 | + issuance.sequence = sle.getFieldU32(ripple::sfSequence); |
| 63 | + auto const flags = sle.getFieldU32(ripple::sfFlags); |
| 64 | + |
| 65 | + if ((flags & ripple::lsfMPTLocked) != 0u) |
| 66 | + issuance.mptLocked = true; |
| 67 | + if ((flags & ripple::lsfMPTCanLock) != 0u) |
| 68 | + issuance.mptCanLock = true; |
| 69 | + if ((flags & ripple::lsfMPTRequireAuth) != 0u) |
| 70 | + issuance.mptRequireAuth = true; |
| 71 | + if ((flags & ripple::lsfMPTCanEscrow) != 0u) |
| 72 | + issuance.mptCanEscrow = true; |
| 73 | + if ((flags & ripple::lsfMPTCanTrade) != 0u) |
| 74 | + issuance.mptCanTrade = true; |
| 75 | + if ((flags & ripple::lsfMPTCanTransfer) != 0u) |
| 76 | + issuance.mptCanTransfer = true; |
| 77 | + if ((flags & ripple::lsfMPTCanClawback) != 0u) |
| 78 | + issuance.mptCanClawback = true; |
| 79 | + |
| 80 | + if (sle.isFieldPresent(ripple::sfTransferFee)) |
| 81 | + issuance.transferFee = sle.getFieldU16(ripple::sfTransferFee); |
| 82 | + |
| 83 | + if (sle.isFieldPresent(ripple::sfAssetScale)) |
| 84 | + issuance.assetScale = sle.getFieldU8(ripple::sfAssetScale); |
| 85 | + |
| 86 | + if (sle.isFieldPresent(ripple::sfMaximumAmount)) |
| 87 | + issuance.maximumAmount = sle.getFieldU64(ripple::sfMaximumAmount); |
| 88 | + |
| 89 | + if (sle.isFieldPresent(ripple::sfOutstandingAmount)) |
| 90 | + issuance.outstandingAmount = sle.getFieldU64(ripple::sfOutstandingAmount); |
| 91 | + |
| 92 | + if (sle.isFieldPresent(ripple::sfLockedAmount)) |
| 93 | + issuance.lockedAmount = sle.getFieldU64(ripple::sfLockedAmount); |
| 94 | + |
| 95 | + if (sle.isFieldPresent(ripple::sfMPTokenMetadata)) |
| 96 | + issuance.mptokenMetadata = ripple::strHex(sle.getFieldVL(ripple::sfMPTokenMetadata)); |
| 97 | + |
| 98 | + if (sle.isFieldPresent(ripple::sfDomainID)) |
| 99 | + issuance.domainID = ripple::strHex(sle.getFieldH256(ripple::sfDomainID)); |
| 100 | + |
| 101 | + issuances.push_back(issuance); |
| 102 | +} |
| 103 | + |
| 104 | +AccountMPTokenIssuancesHandler::Result |
| 105 | +AccountMPTokenIssuancesHandler::process(AccountMPTokenIssuancesHandler::Input const& input, Context const& ctx) const |
| 106 | +{ |
| 107 | + auto const range = sharedPtrBackend_->fetchLedgerRange(); |
| 108 | + ASSERT(range.has_value(), "AccountMPTokenIssuances' ledger range must be available"); |
| 109 | + auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( |
| 110 | + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence |
| 111 | + ); |
| 112 | + |
| 113 | + if (!expectedLgrInfo.has_value()) |
| 114 | + return Error{expectedLgrInfo.error()}; |
| 115 | + |
| 116 | + auto const& lgrInfo = expectedLgrInfo.value(); |
| 117 | + auto const accountID = accountFromStringStrict(input.account); |
| 118 | + auto const accountLedgerObject = |
| 119 | + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*accountID).key, lgrInfo.seq, ctx.yield); |
| 120 | + |
| 121 | + if (not accountLedgerObject) |
| 122 | + return Error{Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"}}; |
| 123 | + |
| 124 | + Output response; |
| 125 | + response.issuances.reserve(input.limit); |
| 126 | + |
| 127 | + auto const addToResponse = [&](ripple::SLE const& sle) { |
| 128 | + if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) { |
| 129 | + addMPTokenIssuance(response.issuances, sle, *accountID); |
| 130 | + } |
| 131 | + }; |
| 132 | + |
| 133 | + auto const expectedNext = traverseOwnedNodes( |
| 134 | + *sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse |
| 135 | + ); |
| 136 | + |
| 137 | + if (!expectedNext.has_value()) |
| 138 | + return Error{expectedNext.error()}; |
| 139 | + |
| 140 | + auto const nextMarker = expectedNext.value(); |
| 141 | + |
| 142 | + response.account = input.account; |
| 143 | + response.limit = input.limit; |
| 144 | + |
| 145 | + response.ledgerHash = ripple::strHex(lgrInfo.hash); |
| 146 | + response.ledgerIndex = lgrInfo.seq; |
| 147 | + |
| 148 | + if (nextMarker.isNonZero()) |
| 149 | + response.marker = nextMarker.toString(); |
| 150 | + |
| 151 | + return response; |
| 152 | +} |
| 153 | + |
| 154 | +AccountMPTokenIssuancesHandler::Input |
| 155 | +tag_invoke(boost::json::value_to_tag<AccountMPTokenIssuancesHandler::Input>, boost::json::value const& jv) |
| 156 | +{ |
| 157 | + auto input = AccountMPTokenIssuancesHandler::Input{}; |
| 158 | + auto const& jsonObject = jv.as_object(); |
| 159 | + |
| 160 | + input.account = boost::json::value_to<std::string>(jv.at(JS(account))); |
| 161 | + |
| 162 | + if (jsonObject.contains(JS(limit))) |
| 163 | + input.limit = util::integralValueAs<uint32_t>(jv.at(JS(limit))); |
| 164 | + |
| 165 | + if (jsonObject.contains(JS(marker))) |
| 166 | + input.marker = boost::json::value_to<std::string>(jv.at(JS(marker))); |
| 167 | + |
| 168 | + if (jsonObject.contains(JS(ledger_hash))) |
| 169 | + input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash))); |
| 170 | + |
| 171 | + if (jsonObject.contains(JS(ledger_index))) { |
| 172 | + if (!jsonObject.at(JS(ledger_index)).is_string()) { |
| 173 | + input.ledgerIndex = util::integralValueAs<uint32_t>(jv.at(JS(ledger_index))); |
| 174 | + } else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") { |
| 175 | + input.ledgerIndex = std::stoi(boost::json::value_to<std::string>(jv.at(JS(ledger_index)))); |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + return input; |
| 180 | +} |
| 181 | + |
| 182 | +void |
| 183 | +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountMPTokenIssuancesHandler::Output const& output) |
| 184 | +{ |
| 185 | + using boost::json::value_from; |
| 186 | + |
| 187 | + auto obj = boost::json::object{ |
| 188 | + {JS(account), output.account}, |
| 189 | + {JS(ledger_hash), output.ledgerHash}, |
| 190 | + {JS(ledger_index), output.ledgerIndex}, |
| 191 | + {JS(validated), output.validated}, |
| 192 | + {JS(limit), output.limit}, |
| 193 | + {"mpt_issuances", value_from(output.issuances)}, |
| 194 | + }; |
| 195 | + |
| 196 | + if (output.marker) |
| 197 | + obj[JS(marker)] = output.marker.value(); |
| 198 | + |
| 199 | + jv = std::move(obj); |
| 200 | +} |
| 201 | + |
| 202 | +void |
| 203 | +tag_invoke( |
| 204 | + boost::json::value_from_tag, |
| 205 | + boost::json::value& jv, |
| 206 | + AccountMPTokenIssuancesHandler::MPTokenIssuancesResponse const& issuance |
| 207 | +) |
| 208 | +{ |
| 209 | + auto obj = boost::json::object{ |
| 210 | + {JS(issuer), issuance.issuer}, |
| 211 | + {JS(sequence), issuance.sequence}, |
| 212 | + }; |
| 213 | + |
| 214 | + if (issuance.transferFee) |
| 215 | + obj["transfer_fee"] = *issuance.transferFee; |
| 216 | + if (issuance.assetScale) |
| 217 | + obj["asset_scale"] = *issuance.assetScale; |
| 218 | + if (issuance.maximumAmount) |
| 219 | + obj["maximum_amount"] = *issuance.maximumAmount; |
| 220 | + if (issuance.outstandingAmount) |
| 221 | + obj["outstanding_amount"] = *issuance.outstandingAmount; |
| 222 | + if (issuance.lockedAmount) |
| 223 | + obj["locked_amount"] = *issuance.lockedAmount; |
| 224 | + if (issuance.mptokenMetadata) |
| 225 | + obj["mptoken_metadata"] = *issuance.mptokenMetadata; |
| 226 | + if (issuance.domainID) |
| 227 | + obj["domain_id"] = *issuance.domainID; |
| 228 | + |
| 229 | + if (issuance.mptLocked) |
| 230 | + obj["mpt_locked"] = *issuance.mptLocked; |
| 231 | + if (issuance.mptCanLock) |
| 232 | + obj["mpt_can_lock"] = *issuance.mptCanLock; |
| 233 | + if (issuance.mptRequireAuth) |
| 234 | + obj["mpt_require_auth"] = *issuance.mptRequireAuth; |
| 235 | + if (issuance.mptCanEscrow) |
| 236 | + obj["mpt_can_escrow"] = *issuance.mptCanEscrow; |
| 237 | + if (issuance.mptCanTrade) |
| 238 | + obj["mpt_can_trade"] = *issuance.mptCanTrade; |
| 239 | + if (issuance.mptCanTransfer) |
| 240 | + obj["mpt_can_transfer"] = *issuance.mptCanTransfer; |
| 241 | + if (issuance.mptCanClawback) |
| 242 | + obj["mpt_can_clawback"] = *issuance.mptCanClawback; |
| 243 | + |
| 244 | + jv = std::move(obj); |
| 245 | +} |
| 246 | + |
| 247 | +} // namespace rpc |
0 commit comments