mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-04 18:55:49 +00:00
first version of URIToken amendment
This commit is contained in:
@@ -447,6 +447,7 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/tx/impl/SignerEntries.cpp
|
||||
src/ripple/app/tx/impl/Taker.cpp
|
||||
src/ripple/app/tx/impl/Transactor.cpp
|
||||
src/ripple/app/tx/impl/URIToken.cpp
|
||||
src/ripple/app/tx/impl/apply.cpp
|
||||
src/ripple/app/tx/impl/applySteps.cpp
|
||||
src/ripple/app/hook/impl/applyHook.cpp
|
||||
|
||||
@@ -392,6 +392,7 @@ LedgerEntryTypesMatch::visitEntry(
|
||||
case ltEMITTED_TXN:
|
||||
case ltNFTOKEN_PAGE:
|
||||
case ltNFTOKEN_OFFER:
|
||||
case ltURI_TOKEN:
|
||||
break;
|
||||
default:
|
||||
invalidTypeAdded_ = true;
|
||||
|
||||
742
src/ripple/app/tx/impl/URIToken.cpp
Normal file
742
src/ripple/app/tx/impl/URIToken.cpp
Normal file
@@ -0,0 +1,742 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2017 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or 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 <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/tx/impl/URIToken.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/TER.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/*
|
||||
* Mint: URI populated only
|
||||
* Burn: URITokenID populated, Blank but present URI
|
||||
* Buy: URITokenID, Amount
|
||||
* Sell: URITokenID, Amount, [Destination], flags=tfSell,
|
||||
* Clear: URITokenID only (clear current sell offer)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
inline URIOperation inferOperation(STTx const& tx)
|
||||
{
|
||||
uint32_t const flags = tx.getFlags();
|
||||
bool const hasURI = tx.isFieldPresent(sfURI);
|
||||
bool const blankURI = hasURI && tx.getFieldVL(sfURI).empty();
|
||||
bool const hasID = tx.isFieldPresent(sfURITokenID);
|
||||
bool const hasAmt = tx.isFieldPresent(sfAmount);
|
||||
bool const hasDst = tx.isFieldPresent(sfDestination);
|
||||
bool const hasSellFlag = flags == tfSell;
|
||||
bool const hasBurnFlag = flags == tfBurnable;
|
||||
bool const blankFlags = flags == 0;
|
||||
|
||||
uint8_t combination =
|
||||
(hasURI ? 0b10000000U : 0) +
|
||||
(blankURI ? 0b01000000U : 0) +
|
||||
(hasID ? 0b00100000U : 0) +
|
||||
(hasAmt ? 0b00010000U : 0) +
|
||||
(hasDst ? 0b00001000U : 0) +
|
||||
(hasSellFlag ? 0b00000100U : 0) +
|
||||
(hasBurnFlag ? 0b00000010U : 0) +
|
||||
(blankFlags ? 0b00000001U : 0);
|
||||
|
||||
switch (combination)
|
||||
{
|
||||
case 0b10000001U:
|
||||
case 0b10000010U:
|
||||
return URIOperation::Mint;
|
||||
case 0b11100001U:
|
||||
return URIOperation::Burn;
|
||||
case 0b00110001U:
|
||||
return URIOperation::Buy;
|
||||
case 0b00110100U:
|
||||
case 0b00111100U:
|
||||
return URIOperation::Sell;
|
||||
case 0b00100001U:
|
||||
return URIOperation::Clear;
|
||||
default:
|
||||
return URIOperation::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
NotTEC
|
||||
URIToken::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureURIToken))
|
||||
return temDISABLED;
|
||||
|
||||
NotTEC const ret{preflight1(ctx)};
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto const op = inferOperation(ctx.tx);
|
||||
if (op == URIOperation::Invalid)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Malformed transaction. Check flags/fields, "
|
||||
<< "documentation for how to specify Mint/Burn/Buy/Sell operations.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
JLOG(ctx.j.trace())
|
||||
<< "URIToken txnid=" << ctx.tx.getTransactionID() << " inferred operation="
|
||||
<< (op == URIOperation::Invalid ? "Invalid" :
|
||||
(op == URIOperation::Mint ? "Mint" :
|
||||
(op == URIOperation::Burn ? "Burn" :
|
||||
(op == URIOperation::Buy ? "Buy" :
|
||||
(op == URIOperation::Sell ? "Sell" :
|
||||
(op == URIOperation::Clear ? "Clear" : "Unknown")))))) << "\n";
|
||||
|
||||
if (op == URIOperation::Mint && ctx.tx.getFieldVL(sfURI).size() > 256)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Malformed transaction. URI may not exceed 256 bytes.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfAmount))
|
||||
{
|
||||
STAmount const amt = ctx.tx.getFieldAmount(sfAmount);
|
||||
if (!isLegalNet(amt) || amt.signum() <= 0)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Malformed transaction. Negative or invalid amount specified.";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (badCurrency() == amt.getCurrency())
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Malformed transaction. Bad currency.";
|
||||
return temBAD_CURRENCY;
|
||||
}
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
URIToken::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const acc = ctx.tx.getAccountID(sfAccount);
|
||||
URIOperation op = inferOperation(ctx.tx);
|
||||
|
||||
std::shared_ptr<SLE const> sleU;
|
||||
if (ctx.tx.isFieldPresent(sfURITokenID))
|
||||
sleU = ctx.view.read(Keylet {ltURI_TOKEN, ctx.tx.getFieldH256(sfURITokenID)});
|
||||
|
||||
uint32_t leFlags = sleU ? sleU->getFieldU32(sfFlags) : 0;
|
||||
std::optional<AccountID> issuer;
|
||||
std::optional<AccountID> owner;
|
||||
std::optional<STAmount> saleAmount;
|
||||
std::optional<AccountID> dest;
|
||||
|
||||
std::shared_ptr<SLE const> sleOwner;
|
||||
|
||||
if (sleU)
|
||||
{
|
||||
if (sleU->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
owner = sleU->getAccountID(sfOwner);
|
||||
issuer = sleU->getAccountID(sfIssuer);
|
||||
if (sleU->isFieldPresent(sfAmount))
|
||||
saleAmount = sleU->getFieldAmount(sfAmount);
|
||||
|
||||
if (sleU->isFieldPresent(sfDestination))
|
||||
dest = sleU->getAccountID(sfDestination);
|
||||
|
||||
sleOwner = ctx.view.read(keylet::account(*owner));
|
||||
if (!sleOwner)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Malformed transaction: owner of URIToken is not in the ledger.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
}
|
||||
else if (op != URIOperation::Mint)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case URIOperation::Mint:
|
||||
{
|
||||
// check if this token has already been minted.
|
||||
if (ctx.view.exists(keylet::uritoken(acc, ctx.tx.getFieldVL(sfURI))))
|
||||
return tecDUPLICATE;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
case URIOperation::Burn:
|
||||
{
|
||||
if (leFlags == tfBurnable && acc == *issuer)
|
||||
{
|
||||
// pass, the issuer can burn the URIToken if they minted it with a burn flag
|
||||
}
|
||||
else
|
||||
if (acc == *owner)
|
||||
{
|
||||
// pass, the owner can always destroy their own URI token
|
||||
}
|
||||
else
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
case URIOperation::Buy:
|
||||
{
|
||||
// if the owner is the account then the buy operation is a clear operation
|
||||
// and we won't bother to check anything else
|
||||
if (acc == *owner)
|
||||
return tesSUCCESS;
|
||||
|
||||
// check if the seller has listed it at all
|
||||
if (!saleAmount)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// check if the seller has listed it for sale to a specific account
|
||||
if (dest && *dest != acc)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// check if the buyer is paying enough
|
||||
STAmount const purchaseAmount = ctx.tx[sfAmount];
|
||||
|
||||
if (purchaseAmount.issue() != saleAmount->issue())
|
||||
return tecNFTOKEN_BUY_SELL_MISMATCH;
|
||||
|
||||
if (purchaseAmount < saleAmount)
|
||||
return tecINSUFFICIENT_PAYMENT;
|
||||
|
||||
if (purchaseAmount.native() && saleAmount->native())
|
||||
{
|
||||
// if it's an xrp sale/purchase then no trustline needed
|
||||
if (purchaseAmount > (sleOwner->getFieldAmount(sfBalance) - ctx.tx[sfFee]))
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// execution to here means it's an IOU sale
|
||||
// check if the buyer has the right trustline with an adequate balance
|
||||
|
||||
STAmount availableFunds{accountFunds(
|
||||
ctx.view,
|
||||
acc,
|
||||
purchaseAmount,
|
||||
fhZERO_IF_FROZEN,
|
||||
ctx.j)};
|
||||
|
||||
if (purchaseAmount > availableFunds)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
case URIOperation::Clear:
|
||||
{
|
||||
if (acc != *owner)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
case URIOperation::Sell:
|
||||
{
|
||||
if (acc != *owner)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!isXRP(*saleAmount))
|
||||
{
|
||||
AccountID const iouIssuer = saleAmount->getIssuer();
|
||||
if (!ctx.view.exists(keylet::account(iouIssuer)))
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "URIToken txid=" << ctx.tx.getTransactionID() << " preclaim with URIOperation::Invalid\n";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TER
|
||||
URIToken::doApply()
|
||||
{
|
||||
auto j = ctx_.app.journal("View");
|
||||
URIOperation op = inferOperation(ctx_.tx);
|
||||
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
if (op == URIOperation::Mint || op == URIOperation::Buy)
|
||||
{
|
||||
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (mPriorBalance - ctx_.tx.getFieldAmount(sfFee).xrp() < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE> sleU;
|
||||
std::optional<AccountID> issuer;
|
||||
std::optional<AccountID> owner;
|
||||
std::optional<STAmount> saleAmount;
|
||||
std::optional<AccountID> dest;
|
||||
std::optional<Keylet> kl;
|
||||
std::shared_ptr<SLE> sleOwner;
|
||||
|
||||
if (op != URIOperation::Mint)
|
||||
{
|
||||
kl = Keylet {ltURI_TOKEN, ctx_.tx.getFieldH256(sfURITokenID)};
|
||||
sleU = view().peek(*kl);
|
||||
|
||||
if (!sleU)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (sleU->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
owner = (*sleU)[sfOwner];
|
||||
issuer = (*sleU)[sfIssuer];
|
||||
saleAmount = (*sleU)[~sfAmount];
|
||||
dest = (*sleU)[~sfDestination];
|
||||
|
||||
if (*owner == account_)
|
||||
sleOwner = sle;
|
||||
else
|
||||
sleOwner = view().peek(keylet::account(*owner));
|
||||
|
||||
if (!sleOwner)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "Malformed transaction: owner of URIToken is not in the ledger.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case URIOperation::Mint:
|
||||
{
|
||||
|
||||
kl = keylet::uritoken(account_, ctx_.tx.getFieldVL(sfURI));
|
||||
if (view().exists(*kl))
|
||||
return tecDUPLICATE;
|
||||
|
||||
sleU = std::make_shared<SLE>(*kl);
|
||||
sleU->setAccountID(sfOwner, account_);
|
||||
sleU->setAccountID(sfIssuer, account_);
|
||||
sleU->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
|
||||
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(account_),
|
||||
*kl,
|
||||
describeOwnerDir(account_));
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< "Adding URIToken to owner directory "
|
||||
<< to_string(kl->key) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
|
||||
sleU->setFieldU64(sfOwnerNode, *page);
|
||||
view().insert(sleU);
|
||||
|
||||
adjustOwnerCount(view(), sle, 1, j);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
case URIOperation::Clear:
|
||||
{
|
||||
sleU->makeFieldAbsent(sfAmount);
|
||||
if (sleU->isFieldPresent(sfDestination))
|
||||
sleU->makeFieldAbsent(sfDestination);
|
||||
view().update(sleU);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
case URIOperation::Buy:
|
||||
{
|
||||
if (account_ == *owner)
|
||||
{
|
||||
// this is a clear operation
|
||||
sleU->makeFieldAbsent(sfAmount);
|
||||
if (sleU->isFieldPresent(sfDestination))
|
||||
sleU->makeFieldAbsent(sfDestination);
|
||||
view().update(sleU);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
STAmount const purchaseAmount = ctx_.tx.getFieldAmount(sfAmount);
|
||||
|
||||
bool const sellerLow = purchaseAmount.getIssuer() > *owner;
|
||||
bool const buyerLow = purchaseAmount.getIssuer() > account_;
|
||||
|
||||
// check if the seller has listed it at all
|
||||
if (!saleAmount)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// check if the seller has listed it for sale to a specific account
|
||||
if (dest && *dest != account_)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (purchaseAmount.issue() != saleAmount->issue())
|
||||
return tecNFTOKEN_BUY_SELL_MISMATCH;
|
||||
|
||||
std::optional<STAmount> initBuyerBal;
|
||||
std::optional<STAmount> initSellerBal;
|
||||
std::optional<STAmount> finBuyerBal;
|
||||
std::optional<STAmount> finSellerBal;
|
||||
std::optional<STAmount> dstAmt;
|
||||
std::optional<Keylet> tlSeller;
|
||||
std::shared_ptr<SLE> sleDstLine;
|
||||
std::shared_ptr<SLE> sleSrcLine;
|
||||
|
||||
// if it's an xrp sale/purchase then no trustline needed
|
||||
if (purchaseAmount.native())
|
||||
{
|
||||
if (purchaseAmount < saleAmount)
|
||||
return tecINSUFFICIENT_PAYMENT;
|
||||
|
||||
if (purchaseAmount > ((*sleOwner)[sfBalance] - ctx_.tx[sfFee]))
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
dstAmt = purchaseAmount;
|
||||
|
||||
initSellerBal = (*sleOwner)[sfBalance];
|
||||
initBuyerBal = (*sle)[sfBalance];
|
||||
|
||||
finSellerBal = *initSellerBal + purchaseAmount;
|
||||
finBuyerBal = *initBuyerBal - purchaseAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// IOU sale
|
||||
|
||||
STAmount availableFunds{accountFunds(
|
||||
view(),
|
||||
account_,
|
||||
purchaseAmount,
|
||||
fhZERO_IF_FROZEN,
|
||||
j)};
|
||||
|
||||
if (purchaseAmount > availableFunds)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
|
||||
// check if the seller has a line
|
||||
tlSeller =
|
||||
keylet::line(*owner, purchaseAmount.getIssuer(), purchaseAmount.getCurrency());
|
||||
Keylet tlBuyer =
|
||||
keylet::line(account_, purchaseAmount.getIssuer(), purchaseAmount.getCurrency());
|
||||
|
||||
sleDstLine = view().peek(*tlSeller);
|
||||
sleSrcLine = view().peek(tlBuyer);
|
||||
|
||||
if (!sleDstLine)
|
||||
{
|
||||
// they do not, so we can create one if they have sufficient reserve
|
||||
|
||||
if (std::uint32_t const ownerCount = {sleOwner->at(sfOwnerCount)};
|
||||
(*sleOwner)[sfBalance] < view().fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(j_.trace()) << "Trust line does not exist. "
|
||||
"Insufficent reserve to create line.";
|
||||
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
}
|
||||
}
|
||||
|
||||
// remove from buyer
|
||||
initBuyerBal = buyerLow ? (*sleSrcLine)[sfBalance] : -(*sleSrcLine)[sfBalance];
|
||||
finBuyerBal = *initBuyerBal - purchaseAmount;
|
||||
|
||||
// compute amount to deliver
|
||||
static Rate const parityRate(QUALITY_ONE);
|
||||
auto xferRate = transferRate(view(), saleAmount->getIssuer());
|
||||
dstAmt =
|
||||
xferRate == parityRate
|
||||
? purchaseAmount
|
||||
: multiplyRound(purchaseAmount, xferRate, purchaseAmount.issue(), true);
|
||||
|
||||
if (!sellerLow)
|
||||
dstAmt->negate();
|
||||
|
||||
initSellerBal = !sleDstLine
|
||||
? purchaseAmount.zeroed()
|
||||
: (sellerLow ? (*sleDstLine)[sfBalance] : -(*sleDstLine)[sfBalance]);
|
||||
|
||||
finSellerBal = *initSellerBal + *dstAmt;
|
||||
|
||||
}
|
||||
|
||||
// sanity check balance mutations (xrp or iou, both are checked the same way now)
|
||||
if (*finSellerBal < *initSellerBal)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
|
||||
<< "finSellerBal < initSellerBal";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (*finBuyerBal > *initBuyerBal)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
|
||||
<< "finBuyerBal > initBuyerBal";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (*finBuyerBal < beast::zero)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
|
||||
<< "finBuyerBal < 0";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (*finSellerBal < beast::zero)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
|
||||
<< "finSellerBal < 0";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
// to this point no ledger changes have been made
|
||||
// make them in a sensible order such that failure doesn't require cleanup
|
||||
|
||||
// add to new owner's directory first, this can fail if they have too many objects
|
||||
auto const newPage = view().dirInsert(
|
||||
keylet::ownerDir(account_),
|
||||
*kl,
|
||||
describeOwnerDir(account_));
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< "Adding URIToken to owner directory "
|
||||
<< to_string(kl->key) << ": "
|
||||
<< (newPage ? "success" : "failure");
|
||||
|
||||
if (!newPage)
|
||||
{
|
||||
// nothing has happened at all and there is nothing to clean up
|
||||
// we can just leave with DIR_FULL
|
||||
return tecDIR_FULL;
|
||||
}
|
||||
|
||||
// Next create destination trustline where applicable. This could fail for a variety of reasons.
|
||||
// If it does fail we need to remove the dir entry we just added to the buyer before we leave.
|
||||
bool lineCreated = false;
|
||||
if (!isXRP(purchaseAmount) && !sleDstLine)
|
||||
{
|
||||
// clang-format off
|
||||
if (TER const ter = trustCreate(
|
||||
view(), // payment sandbox
|
||||
sellerLow, // is dest low?
|
||||
*issuer, // source
|
||||
*owner, // destination
|
||||
tlSeller->key, // ledger index
|
||||
sleOwner, // Account to add to
|
||||
false, // authorize account
|
||||
(sleOwner->getFlags() & lsfDefaultRipple) == 0,
|
||||
false, // freeze trust line
|
||||
*dstAmt, // initial balance zero
|
||||
Issue(purchaseAmount.getCurrency(), *owner), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
j); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
// remove the newly inserted directory entry before we leave
|
||||
//
|
||||
if (!view().dirRemove(keylet::ownerDir(account_), *newPage, kl->key, true))
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Could not remove URIToken from owner directory";
|
||||
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
|
||||
// leave
|
||||
return ter;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
// add their trustline to their ownercount
|
||||
lineCreated = true;
|
||||
}
|
||||
|
||||
// execution to here means we added the URIToken to the buyer's directory
|
||||
// and we definitely have a way to send the funds to the seller.
|
||||
|
||||
// remove from current owner directory
|
||||
if (!view().dirRemove(keylet::ownerDir(*owner), sleU->getFieldU64(sfOwnerNode), kl->key, true))
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Could not remove URIToken from owner directory";
|
||||
|
||||
// remove the newly inserted directory entry before we leave
|
||||
if (!view().dirRemove(keylet::ownerDir(account_), *newPage, kl->key, true))
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Could not remove URIToken from owner directory (2)";
|
||||
}
|
||||
|
||||
// clean up any trustline we might have made
|
||||
if (lineCreated)
|
||||
{
|
||||
auto line = view().peek(*tlSeller);
|
||||
if (line)
|
||||
view().erase(line);
|
||||
}
|
||||
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
|
||||
// above is all the things that could fail. we now have swapped the ownership as far as the ownerdirs
|
||||
// are concerned, and we have a place to pay to and from.
|
||||
|
||||
// if a trustline was created then the ownercount stays the same on the seller +1 TL -1 URIToken
|
||||
if (!lineCreated)
|
||||
adjustOwnerCount(view(), sleOwner, -1, j);
|
||||
|
||||
// the buyer gets a new object
|
||||
adjustOwnerCount(view(), sle, 1, j);
|
||||
|
||||
// clean the offer off the object
|
||||
sleU->makeFieldAbsent(sfAmount);
|
||||
if (sleU->isFieldPresent(sfDestination))
|
||||
sleU->makeFieldAbsent(sfDestination);
|
||||
|
||||
// set the new owner of the object
|
||||
sleU->setAccountID(sfOwner, account_);
|
||||
|
||||
// tell the ledger where to find it
|
||||
sleU->setFieldU64(sfOwnerNode, *newPage);
|
||||
|
||||
|
||||
// update the buyer's balance
|
||||
if (isXRP(purchaseAmount))
|
||||
{
|
||||
// the sale is for xrp, so set the balance
|
||||
sle->setFieldAmount(sfBalance, *finBuyerBal);
|
||||
}
|
||||
else if (sleSrcLine)
|
||||
{
|
||||
// update the buyer's line to reflect the reduction of the purchase price
|
||||
sleSrcLine->setFieldAmount(sfBalance, buyerLow ? *finBuyerBal : -(*finBuyerBal));
|
||||
}
|
||||
else
|
||||
return tecINTERNAL;
|
||||
|
||||
// update the seller's balance
|
||||
if (isXRP(purchaseAmount))
|
||||
{
|
||||
// the sale is for xrp, so set the balance
|
||||
sleOwner->setFieldAmount(sfBalance, *finSellerBal);
|
||||
}
|
||||
else if (sleDstLine)
|
||||
{
|
||||
// the line already existed on the seller side so update it
|
||||
sleDstLine->setFieldAmount(sfBalance, sellerLow ? *finSellerBal : -(*finSellerBal));
|
||||
}
|
||||
else if (lineCreated)
|
||||
{
|
||||
// pass, the TL already has this balance set on it at creation
|
||||
}
|
||||
else
|
||||
return tecINTERNAL;
|
||||
|
||||
|
||||
if (sleSrcLine)
|
||||
view().update(sleSrcLine);
|
||||
if (sleDstLine)
|
||||
view().update(sleDstLine);
|
||||
|
||||
view().update(sleU);
|
||||
view().update(sleOwner);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
case URIOperation::Burn:
|
||||
{
|
||||
if (sleU->getAccountID(sfOwner) == account_)
|
||||
{
|
||||
// pass, owner may always delete own object
|
||||
}
|
||||
else if (sleU->getAccountID(sfIssuer) == account_ && (sleU->getFlags() & tfBurnable))
|
||||
{
|
||||
// pass, issuer may burn if the tfBurnable flag was set during minting
|
||||
}
|
||||
else
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// execution to here means there is permission to burn
|
||||
|
||||
auto const page = (*sleU)[sfOwnerNode];
|
||||
if (!view().dirRemove(keylet::ownerDir(*owner), page, kl->key, true))
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Could not remove URIToken from owner directory";
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
|
||||
view().erase(sleU);
|
||||
adjustOwnerCount(view(), sle, -1, j);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
case URIOperation::Sell:
|
||||
{
|
||||
if (account_ != *owner)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const txDest = ctx_.tx[~sfDestination];
|
||||
|
||||
// update destination where applicable
|
||||
if (txDest)
|
||||
sleU->setAccountID(sfDestination, *txDest);
|
||||
else if (dest)
|
||||
sleU->makeFieldAbsent(sfDestination);
|
||||
|
||||
sleU->setFieldAmount(sfAmount, ctx_.tx[sfAmount]);
|
||||
|
||||
view().update(sleU);
|
||||
std::cout << "sleU on sell: " << (*sleU) << "\n";
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
default:
|
||||
return tecINTERNAL;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
62
src/ripple/app/tx/impl/URIToken.h
Normal file
62
src/ripple/app/tx/impl/URIToken.h
Normal file
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_URITOKEN_H_INCLUDED
|
||||
#define RIPPLE_TX_URITOKEN_H_INCLUDED
|
||||
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
enum struct URIOperation : uint8_t
|
||||
{
|
||||
Invalid = 0,
|
||||
Mint = 1,
|
||||
Burn = 2,
|
||||
Buy = 3,
|
||||
Sell = 4,
|
||||
Clear = 5
|
||||
};
|
||||
|
||||
class URIToken : public Transactor
|
||||
{
|
||||
public:
|
||||
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit URIToken(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -156,6 +156,8 @@ invoke_preflight(PreflightContext const& ctx)
|
||||
return invoke_preflight_helper<ClaimReward>(ctx);
|
||||
case ttINVOKE:
|
||||
return invoke_preflight_helper<Invoke>(ctx);
|
||||
case ttURI_TOKEN:
|
||||
return invoke_preflight_helper<URIToken>(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return {temUNKNOWN, TxConsequences{temUNKNOWN}};
|
||||
@@ -265,6 +267,8 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
return invoke_preclaim<ClaimReward>(ctx);
|
||||
case ttINVOKE:
|
||||
return invoke_preclaim<Invoke>(ctx);
|
||||
case ttURI_TOKEN:
|
||||
return invoke_preclaim<URIToken>(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return temUNKNOWN;
|
||||
@@ -335,6 +339,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return ClaimReward::calculateBaseFee(view, tx);
|
||||
case ttINVOKE:
|
||||
return Invoke::calculateBaseFee(view, tx);
|
||||
case ttURI_TOKEN:
|
||||
return URIToken::calculateBaseFee(view, tx);
|
||||
default:
|
||||
assert(false);
|
||||
return FeeUnit64{0};
|
||||
@@ -501,6 +507,10 @@ invoke_apply(ApplyContext& ctx)
|
||||
Invoke p(ctx);
|
||||
return p();
|
||||
}
|
||||
case ttURI_TOKEN: {
|
||||
URIToken p(ctx);
|
||||
return p();
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
return {temUNKNOWN, false};
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 57;
|
||||
static constexpr std::size_t numFeatures = 58;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -344,6 +344,7 @@ extern uint256 const featureDisallowIncoming;
|
||||
extern uint256 const fixRemoveNFTokenAutoTrustLine;
|
||||
extern uint256 const featureImmediateOfferKilled;
|
||||
extern uint256 const featurePaychanAndEscrowForTokens;
|
||||
extern uint256 const featureURIToken;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -284,6 +284,9 @@ nft_buys(uint256 const& id) noexcept;
|
||||
Keylet
|
||||
nft_sells(uint256 const& id) noexcept;
|
||||
|
||||
Keylet
|
||||
uritoken(AccountID const& issuer, Blob const& uri);
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
// Everything below is deprecated and should be removed in favor of keylets:
|
||||
|
||||
@@ -161,6 +161,12 @@ enum LedgerEntryType : std::uint16_t
|
||||
*/
|
||||
ltNFTOKEN_OFFER = 0x0037,
|
||||
|
||||
/** A unique ledger object which contains an up to 256 byte URI
|
||||
|
||||
\sa keylet::uritoken
|
||||
*/
|
||||
ltURI_TOKEN = 0x0055,
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
/** A special type, matching any ledger entry type.
|
||||
|
||||
|
||||
@@ -472,7 +472,7 @@ extern SF_UINT256 const sfHookNamespace;
|
||||
extern SF_UINT256 const sfHookSetTxnID;
|
||||
extern SF_UINT256 const sfOfferID;
|
||||
extern SF_UINT256 const sfEscrowID;
|
||||
|
||||
extern SF_UINT256 const sfURITokenID;
|
||||
|
||||
// currency amount (common)
|
||||
extern SF_AMOUNT const sfAmount;
|
||||
|
||||
@@ -139,6 +139,9 @@ enum TxType : std::uint16_t
|
||||
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
|
||||
ttNFTOKEN_ACCEPT_OFFER = 29,
|
||||
|
||||
/** This transaction mints/burns/buys/sells a URI TOKEN */
|
||||
ttURI_TOKEN = 45,
|
||||
|
||||
/** This transaction resets accumulator/counters and claims a reward for holding an average balance
|
||||
* from a specified hook */
|
||||
ttCLAIM_REWARD = 98,
|
||||
|
||||
@@ -454,6 +454,7 @@ REGISTER_FIX (fixRemoveNFTokenAutoTrustLine, Supported::yes, DefaultVote::yes
|
||||
REGISTER_FEATURE(ImmediateOfferKilled, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FEATURE(DisallowIncoming, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FEATURE(PaychanAndEscrowForTokens, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(URIToken, Supported::yes, DefaultVote::yes);
|
||||
|
||||
// The following amendments have been active for at least two years. Their
|
||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||
|
||||
@@ -69,6 +69,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
NFTOKEN_OFFER = 'q',
|
||||
NFTOKEN_BUY_OFFERS = 'h',
|
||||
NFTOKEN_SELL_OFFERS = 'i',
|
||||
URI_TOKEN = 'U',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -414,6 +415,13 @@ nft_sells(uint256 const& id) noexcept
|
||||
return {ltDIR_NODE, indexHash(LedgerNameSpace::NFTOKEN_SELL_OFFERS, id)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
uritoken(AccountID const& issuer, Blob const& uri)
|
||||
{
|
||||
return {
|
||||
ltURI_TOKEN, indexHash(LedgerNameSpace::URI_TOKEN, issuer, uri)};
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -314,6 +314,21 @@ LedgerFormats::LedgerFormats()
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED}
|
||||
},
|
||||
commonFields);
|
||||
|
||||
add(jss::URIToken,
|
||||
ltURI_TOKEN,
|
||||
{
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfURI, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
{sfDestination, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED}
|
||||
},
|
||||
commonFields);
|
||||
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
||||
@@ -224,6 +224,7 @@ CONSTRUCT_TYPED_SFIELD(sfHookNamespace, "HookNamespace", UINT256,
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookSetTxnID, "HookSetTxnID", UINT256, 33);
|
||||
CONSTRUCT_TYPED_SFIELD(sfOfferID, "OfferID", UINT256, 34);
|
||||
CONSTRUCT_TYPED_SFIELD(sfEscrowID, "EscrowID", UINT256, 35);
|
||||
CONSTRUCT_TYPED_SFIELD(sfURITokenID, "URITokenID", UINT256, 36);
|
||||
|
||||
// currency amount (common)
|
||||
CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
|
||||
|
||||
@@ -361,6 +361,16 @@ TxFormats::TxFormats()
|
||||
{sfInvoiceID, soeOPTIONAL},
|
||||
},
|
||||
commonFields);
|
||||
|
||||
add(jss::URIToken,
|
||||
ttURI_TOKEN,
|
||||
{
|
||||
{sfURI, soeOPTIONAL},
|
||||
{sfURITokenID, soeOPTIONAL},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
{sfDestination, soeOPTIONAL}
|
||||
},
|
||||
commonFields);
|
||||
}
|
||||
|
||||
TxFormats const&
|
||||
|
||||
@@ -655,6 +655,7 @@ JSS(unl); // out: UnlList
|
||||
JSS(unlimited); // out: Connection.h
|
||||
JSS(uptime); // out: GetCounts
|
||||
JSS(uri); // out: ValidatorSites
|
||||
JSS(URIToken); // LedgerEntry
|
||||
JSS(url); // in/out: Subscribe, Unsubscribe
|
||||
JSS(url_password); // in: Subscribe
|
||||
JSS(url_username); // in: Subscribe
|
||||
|
||||
Reference in New Issue
Block a user