mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-19 18:15:50 +00:00
Compare commits
5 Commits
nd-simplif
...
uritoken-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80b8207b0d | ||
|
|
0d0adb4236 | ||
|
|
1de0493145 | ||
|
|
164b3a1c3d | ||
|
|
9b7103ea4d |
@@ -492,6 +492,7 @@ LedgerEntryTypesMatch::visitEntry(
|
|||||||
case ltURI_TOKEN:
|
case ltURI_TOKEN:
|
||||||
case ltIMPORT_VLSEQ:
|
case ltIMPORT_VLSEQ:
|
||||||
case ltUNL_REPORT:
|
case ltUNL_REPORT:
|
||||||
|
case ltURI_TOKEN_OFFER:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
invalidTypeAdded_ = true;
|
invalidTypeAdded_ = true;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <ripple/app/ledger/Ledger.h>
|
#include <ripple/app/ledger/Ledger.h>
|
||||||
#include <ripple/app/tx/impl/URIToken.h>
|
#include <ripple/app/tx/impl/URIToken.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/ledger/View.h>
|
||||||
#include <ripple/protocol/Feature.h>
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/Indexes.h>
|
#include <ripple/protocol/Indexes.h>
|
||||||
#include <ripple/protocol/Quality.h>
|
#include <ripple/protocol/Quality.h>
|
||||||
@@ -29,6 +30,319 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
|
static TER
|
||||||
|
transfer(
|
||||||
|
Sandbox& sb,
|
||||||
|
AccountID const& buyer,
|
||||||
|
AccountID const& seller,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleBuyer,
|
||||||
|
std::shared_ptr<SLE> const& sleSeller,
|
||||||
|
beast::Journal journal)
|
||||||
|
{
|
||||||
|
// add token to new owner dir
|
||||||
|
auto const newPage = sb.dirInsert(
|
||||||
|
keylet::ownerDir(buyer), kl, describeOwnerDir(buyer));
|
||||||
|
|
||||||
|
JLOG(journal.trace()) << "Adding URIToken to owner directory "
|
||||||
|
<< to_string(kl.key) << ": "
|
||||||
|
<< (newPage ? "success" : "failure");
|
||||||
|
|
||||||
|
if (!newPage)
|
||||||
|
return tecDIR_FULL;
|
||||||
|
|
||||||
|
// remove from current owner directory
|
||||||
|
if (!sb.dirRemove(
|
||||||
|
keylet::ownerDir(seller),
|
||||||
|
sleU->getFieldU64(sfOwnerNode),
|
||||||
|
kl.key,
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
JLOG(journal.fatal())
|
||||||
|
<< "Could not remove URIToken from owner directory";
|
||||||
|
|
||||||
|
return tefBAD_LEDGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust owner counts
|
||||||
|
adjustOwnerCount(sb, sleSeller, -1, journal);
|
||||||
|
adjustOwnerCount(sb, sleBuyer, 1, journal);
|
||||||
|
|
||||||
|
// 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, buyer);
|
||||||
|
|
||||||
|
// tell the ledger where to find it
|
||||||
|
sleU->setFieldU64(sfOwnerNode, *newPage);
|
||||||
|
|
||||||
|
// check each side has sufficient balance remaining to cover the
|
||||||
|
// updated ownercounts
|
||||||
|
auto hasSufficientReserve = [&](std::shared_ptr<SLE> const& sle) -> bool {
|
||||||
|
std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount);
|
||||||
|
return sle->getFieldAmount(sfBalance) >=
|
||||||
|
sb.fees().accountReserve(uOwnerCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!hasSufficientReserve(sleBuyer))
|
||||||
|
{
|
||||||
|
JLOG(journal.trace()) << "URIToken: buyer " << buyer
|
||||||
|
<< " has insufficient reserve to buy";
|
||||||
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should only happen if the owner burned their reserves
|
||||||
|
// below the needed amount via another transactor. If this
|
||||||
|
// happens they should top up their account before selling!
|
||||||
|
if (!hasSufficientReserve(sleSeller))
|
||||||
|
{
|
||||||
|
JLOG(journal.warn()) << "URIToken: seller " << seller
|
||||||
|
<< " has insufficient reserve to allow purchase!";
|
||||||
|
return tecINSUF_RESERVE_SELLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.update(sleU);
|
||||||
|
sb.update(sleBuyer);
|
||||||
|
sb.update(sleSeller);
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TER
|
||||||
|
fullSwap(
|
||||||
|
Sandbox& sb,
|
||||||
|
STAmount const& priorBalance,
|
||||||
|
STAmount const& purchaseAmount,
|
||||||
|
STAmount const& saleAmount,
|
||||||
|
STAmount const& fee,
|
||||||
|
AccountID const& buyer,
|
||||||
|
AccountID const& seller,
|
||||||
|
AccountID const& issuer,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleBuyer,
|
||||||
|
std::shared_ptr<SLE> const& sleSeller,
|
||||||
|
beast::Journal journal)
|
||||||
|
{
|
||||||
|
if (purchaseAmount < saleAmount)
|
||||||
|
return tecINSUFFICIENT_PAYMENT;
|
||||||
|
|
||||||
|
// if it's an xrp sale/purchase then no trustline needed
|
||||||
|
if (purchaseAmount.native())
|
||||||
|
{
|
||||||
|
STAmount needed{sb.fees().accountReserve(
|
||||||
|
sleBuyer->getFieldU32(sfOwnerCount) + 1)};
|
||||||
|
|
||||||
|
// STAmount const fee = ctx_.tx.getFieldAmount(sfFee).xrp();
|
||||||
|
|
||||||
|
if (needed + fee < needed)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
needed += fee;
|
||||||
|
|
||||||
|
if (needed + purchaseAmount < needed)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
needed += purchaseAmount;
|
||||||
|
|
||||||
|
if (needed > priorBalance)
|
||||||
|
return tecINSUFFICIENT_FUNDS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// IOU sale
|
||||||
|
if (TER result = trustTransferAllowed(
|
||||||
|
sb, {buyer, seller}, purchaseAmount.issue(), journal);
|
||||||
|
!isTesSuccess(result))
|
||||||
|
{
|
||||||
|
JLOG(journal.trace())
|
||||||
|
<< "URIToken::doApply trustTransferAllowed result=" << result;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (STAmount availableFunds{accountFunds(
|
||||||
|
sb, buyer, purchaseAmount, fhZERO_IF_FROZEN, journal)};
|
||||||
|
purchaseAmount > availableFunds)
|
||||||
|
return tecINSUFFICIENT_FUNDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute the funds transfer, we'll check reserves last
|
||||||
|
if (TER result =
|
||||||
|
accountSend(sb, buyer, seller, purchaseAmount, journal, false);
|
||||||
|
!isTesSuccess(result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (TER result = transfer(
|
||||||
|
sb,
|
||||||
|
buyer,
|
||||||
|
seller,
|
||||||
|
kl,
|
||||||
|
sleU,
|
||||||
|
sleBuyer,
|
||||||
|
sleSeller,
|
||||||
|
journal);
|
||||||
|
!isTesSuccess(result))
|
||||||
|
{
|
||||||
|
JLOG(journal.fatal())
|
||||||
|
<< "URIToken: transfer failed: " << result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TER
|
||||||
|
offerLock(
|
||||||
|
Sandbox& sb,
|
||||||
|
STAmount const& priorBalance,
|
||||||
|
STAmount const& purchaseAmount,
|
||||||
|
AccountID const& buyer,
|
||||||
|
AccountID const& seller,
|
||||||
|
AccountID const& issuer,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleBuyer,
|
||||||
|
std::shared_ptr<SLE> const& sleSeller,
|
||||||
|
beast::Journal journal)
|
||||||
|
{
|
||||||
|
if (isXRP(purchaseAmount))
|
||||||
|
(*sleBuyer)[sfBalance] = (*sleBuyer)[sfBalance] - purchaseAmount;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if (!sb.rules().enabled(featureRoyalties))
|
||||||
|
// return temDISABLED;
|
||||||
|
|
||||||
|
TER const result = trustTransferAllowed(sb, {buyer, seller}, purchaseAmount.issue(), journal);
|
||||||
|
if (!isTesSuccess(result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
auto sleLine = sb.peek(keylet::line(buyer, purchaseAmount.getIssuer(), purchaseAmount.getCurrency()));
|
||||||
|
if (purchaseAmount.getIssuer() != buyer)
|
||||||
|
{
|
||||||
|
if (!sleLine)
|
||||||
|
return tecNO_LINE;
|
||||||
|
|
||||||
|
TER const result = trustAdjustLockedBalance(sb, sleLine, purchaseAmount, 1, journal, WetRun);
|
||||||
|
if (!isTesSuccess(result))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TER
|
||||||
|
offerUnLock(
|
||||||
|
Sandbox& sb,
|
||||||
|
STAmount const& priorBalance,
|
||||||
|
STAmount const& purchaseAmount,
|
||||||
|
AccountID const& buyer,
|
||||||
|
AccountID const& seller,
|
||||||
|
AccountID const& issuer,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleBuyer,
|
||||||
|
std::shared_ptr<SLE> const& sleSeller,
|
||||||
|
std::shared_ptr<SLE> const& sleIssuer,
|
||||||
|
beast::Journal journal)
|
||||||
|
{
|
||||||
|
STAmount deliverAmount = purchaseAmount;
|
||||||
|
STAmount royaltyAmount = STAmount{0};
|
||||||
|
|
||||||
|
// Amendment Guard
|
||||||
|
if (sleU->isFieldPresent(sfRoyaltyRate))
|
||||||
|
{
|
||||||
|
// auto const royaltyRate = sleU->getFieldU32(sfRoyaltyRate);
|
||||||
|
// royaltyAmount = multiplyRound(
|
||||||
|
// deliverAmount, Rate{royaltyRate}, deliverAmount.issue(), true);
|
||||||
|
// deliverAmount -= royaltyAmount;
|
||||||
|
// STAmount deliverAmount = purchaseAmount;
|
||||||
|
static Rate const parityRate(QUALITY_ONE);
|
||||||
|
auto const royaltyRate = [&]() -> Rate {
|
||||||
|
if (sleU->isFieldPresent(sfRoyaltyRate))
|
||||||
|
return Rate{sleU->getFieldU32(sfRoyaltyRate)};
|
||||||
|
return Rate{QUALITY_ONE};
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (royaltyRate != parityRate)
|
||||||
|
deliverAmount = multiplyRound(purchaseAmount, royaltyRate, purchaseAmount.issue(), true);
|
||||||
|
royaltyAmount = deliverAmount - purchaseAmount;
|
||||||
|
deliverAmount = purchaseAmount - royaltyAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isXRP(purchaseAmount))
|
||||||
|
{
|
||||||
|
(*sleSeller)[sfBalance] = (*sleSeller)[sfBalance] + deliverAmount;
|
||||||
|
(*sleIssuer)[sfBalance] = (*sleIssuer)[sfBalance] + royaltyAmount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if (!sb.rules().enabled(featureRoyalties))
|
||||||
|
// return temDISABLED;
|
||||||
|
|
||||||
|
auto const xferRate = transferRate(sb, deliverAmount.getIssuer());
|
||||||
|
if (TER const result = trustTransferLockedBalance(
|
||||||
|
sb,
|
||||||
|
seller,
|
||||||
|
sleBuyer,
|
||||||
|
sleSeller,
|
||||||
|
deliverAmount,
|
||||||
|
0,
|
||||||
|
xferRate,
|
||||||
|
journal,
|
||||||
|
WetRun); !isTesSuccess(result))
|
||||||
|
{
|
||||||
|
JLOG(journal.fatal())
|
||||||
|
<< "URIToken: trustTransferLockedBalance failed: " << result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (royaltyAmount != beast::zero)
|
||||||
|
{
|
||||||
|
if (TER const result = trustTransferLockedBalance(
|
||||||
|
sb,
|
||||||
|
seller,
|
||||||
|
sleBuyer,
|
||||||
|
sleIssuer,
|
||||||
|
royaltyAmount,
|
||||||
|
0,
|
||||||
|
xferRate,
|
||||||
|
journal,
|
||||||
|
WetRun); !isTesSuccess(result))
|
||||||
|
{
|
||||||
|
JLOG(journal.fatal())
|
||||||
|
<< "URIToken: trustTransferLockedBalance failed: " << result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer the token to the new owner
|
||||||
|
if (TER const result = transfer(
|
||||||
|
sb,
|
||||||
|
buyer,
|
||||||
|
seller,
|
||||||
|
kl,
|
||||||
|
sleU,
|
||||||
|
sleBuyer,
|
||||||
|
sleSeller,
|
||||||
|
journal);
|
||||||
|
!isTesSuccess(result))
|
||||||
|
{
|
||||||
|
JLOG(journal.fatal())
|
||||||
|
<< "URIToken: transfer failed: " << result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.update(sleU);
|
||||||
|
sb.update(sleBuyer);
|
||||||
|
sb.update(sleSeller);
|
||||||
|
sb.update(sleIssuer);
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
NotTEC
|
NotTEC
|
||||||
URIToken::preflight(PreflightContext const& ctx)
|
URIToken::preflight(PreflightContext const& ctx)
|
||||||
{
|
{
|
||||||
@@ -116,6 +430,32 @@ URIToken::preflight(PreflightContext const& ctx)
|
|||||||
case ttURITOKEN_MINT: {
|
case ttURITOKEN_MINT: {
|
||||||
if (flags & tfURITokenMintMask)
|
if (flags & tfURITokenMintMask)
|
||||||
return temINVALID_FLAG;
|
return temINVALID_FLAG;
|
||||||
|
|
||||||
|
// Amendment Guard
|
||||||
|
if (ctx.tx.isFieldPresent(sfRoyaltyRate))
|
||||||
|
{
|
||||||
|
std::uint32_t uRate = ctx.tx.getFieldU32(sfRoyaltyRate);
|
||||||
|
if (uRate == 0)
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.error())
|
||||||
|
<< "Malformed transaction: Royalty rate unset.";
|
||||||
|
return temBAD_TRANSFER_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uRate && (uRate < QUALITY_ONE))
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.error())
|
||||||
|
<< "Malformed transaction: Transfer rate too small.";
|
||||||
|
return temBAD_TRANSFER_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uRate > 2 * QUALITY_ONE)
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.error())
|
||||||
|
<< "Malformed transaction: Transfer rate too large.";
|
||||||
|
return temBAD_TRANSFER_RATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +465,6 @@ URIToken::preflight(PreflightContext const& ctx)
|
|||||||
case ttURITOKEN_CREATE_SELL_OFFER: {
|
case ttURITOKEN_CREATE_SELL_OFFER: {
|
||||||
if (flags & tfURITokenNonMintMask)
|
if (flags & tfURITokenNonMintMask)
|
||||||
return temINVALID_FLAG;
|
return temINVALID_FLAG;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +559,11 @@ URIToken::preclaim(PreclaimContext const& ctx)
|
|||||||
|
|
||||||
// check if the seller has listed it at all
|
// check if the seller has listed it at all
|
||||||
if (!saleAmount)
|
if (!saleAmount)
|
||||||
return tecNO_PERMISSION;
|
{
|
||||||
|
// Amendment Guard
|
||||||
|
// return tecNO_PERMISSION;
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
// check if the seller has listed it for sale to a specific account
|
// check if the seller has listed it for sale to a specific account
|
||||||
if (dest && *dest != acc)
|
if (dest && *dest != acc)
|
||||||
@@ -434,6 +777,11 @@ URIToken::doApply()
|
|||||||
if (flags & tfBurnable)
|
if (flags & tfBurnable)
|
||||||
sleU->setFlag(tfBurnable);
|
sleU->setFlag(tfBurnable);
|
||||||
|
|
||||||
|
// Amendment Guard
|
||||||
|
if (ctx_.tx.isFieldPresent(sfRoyaltyRate))
|
||||||
|
sleU->setFieldU32(
|
||||||
|
sfRoyaltyRate, ctx_.tx.getFieldU32(sfRoyaltyRate));
|
||||||
|
|
||||||
auto const page = sb.dirInsert(
|
auto const page = sb.dirInsert(
|
||||||
keylet::ownerDir(account_), *kl, describeOwnerDir(account_));
|
keylet::ownerDir(account_), *kl, describeOwnerDir(account_));
|
||||||
|
|
||||||
@@ -472,9 +820,71 @@ URIToken::doApply()
|
|||||||
case ttURITOKEN_BUY: {
|
case ttURITOKEN_BUY: {
|
||||||
STAmount const purchaseAmount = ctx_.tx.getFieldAmount(sfAmount);
|
STAmount const purchaseAmount = ctx_.tx.getFieldAmount(sfAmount);
|
||||||
|
|
||||||
|
// OLD Amendment Guard
|
||||||
// check if the seller has listed it at all
|
// check if the seller has listed it at all
|
||||||
if (!saleAmount)
|
// if (!saleAmount)
|
||||||
return tecNO_PERMISSION;
|
// return tecNO_PERMISSION;
|
||||||
|
|
||||||
|
auto const offerMatched = [&]() -> bool {
|
||||||
|
if (purchaseAmount.issue() != saleAmount->issue())
|
||||||
|
return false;
|
||||||
|
if (purchaseAmount != *saleAmount)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!saleAmount || !offerMatched())
|
||||||
|
{
|
||||||
|
// Amendment Guard
|
||||||
|
auto uRate = getRate(purchaseAmount, STAmount{1});
|
||||||
|
auto const buyOfferKey = keylet::uritoken_offer(
|
||||||
|
*kl,
|
||||||
|
uRate,
|
||||||
|
purchaseAmount.getIssuer(),
|
||||||
|
purchaseAmount.getCurrency());
|
||||||
|
|
||||||
|
// Add offer to owner's directory.
|
||||||
|
auto const ownerNode = sb.dirInsert(
|
||||||
|
keylet::ownerDir(account_),
|
||||||
|
buyOfferKey,
|
||||||
|
describeOwnerDir(account_));
|
||||||
|
|
||||||
|
if (!ownerNode)
|
||||||
|
{
|
||||||
|
JLOG(j_.debug()) << "final result: failed to add offer to "
|
||||||
|
"owner's directory";
|
||||||
|
return tecDIR_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update owner count.
|
||||||
|
adjustOwnerCount(sb, sle, 1, j);
|
||||||
|
|
||||||
|
// Create Offer
|
||||||
|
auto sleOffer = std::make_shared<SLE>(buyOfferKey);
|
||||||
|
sleOffer->setAccountID(sfOwner, account_);
|
||||||
|
sleOffer->setFieldH256(sfURITokenID, kl->key);
|
||||||
|
sleOffer->setFieldAmount(sfAmount, purchaseAmount);
|
||||||
|
sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
|
||||||
|
sb.insert(sleOffer);
|
||||||
|
|
||||||
|
// Lock the amount (XRP or IOU) in the offer
|
||||||
|
if (TER const ter = offerLock(
|
||||||
|
sb,
|
||||||
|
mPriorBalance,
|
||||||
|
purchaseAmount,
|
||||||
|
account_, // receiver
|
||||||
|
*owner, // sender
|
||||||
|
*issuer, // issuer
|
||||||
|
*kl,
|
||||||
|
sleU,
|
||||||
|
sle, // receiver sle
|
||||||
|
sleOwner, // sender sle
|
||||||
|
j_); !isTesSuccess(ter))
|
||||||
|
return ter;
|
||||||
|
|
||||||
|
sb.apply(ctx_.rawView());
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
// check if the seller has listed it for sale to a specific account
|
// check if the seller has listed it for sale to a specific account
|
||||||
if (dest && *dest != account_)
|
if (dest && *dest != account_)
|
||||||
@@ -486,129 +896,23 @@ URIToken::doApply()
|
|||||||
if (fixV1)
|
if (fixV1)
|
||||||
{
|
{
|
||||||
// this is the reworked version of the buy routine
|
// this is the reworked version of the buy routine
|
||||||
|
STAmount const fee = ctx_.tx.getFieldAmount(sfFee).xrp();
|
||||||
if (purchaseAmount < saleAmount)
|
if (auto const ter = fullSwap(
|
||||||
return tecINSUFFICIENT_PAYMENT;
|
sb,
|
||||||
|
mPriorBalance,
|
||||||
// if it's an xrp sale/purchase then no trustline needed
|
purchaseAmount,
|
||||||
if (purchaseAmount.native())
|
*saleAmount,
|
||||||
{
|
fee,
|
||||||
STAmount needed{sb.fees().accountReserve(
|
account_, // receiver
|
||||||
sle->getFieldU32(sfOwnerCount) + 1)};
|
*owner, // sender
|
||||||
|
*issuer, // issuer
|
||||||
STAmount const fee = ctx_.tx.getFieldAmount(sfFee).xrp();
|
*kl,
|
||||||
|
sleU,
|
||||||
if (needed + fee < needed)
|
sle, // receiver sle
|
||||||
return tecINTERNAL;
|
sleOwner, // sender sle
|
||||||
|
j_);
|
||||||
needed += fee;
|
!isTesSuccess(ter))
|
||||||
|
return ter;
|
||||||
if (needed + purchaseAmount < needed)
|
|
||||||
return tecINTERNAL;
|
|
||||||
|
|
||||||
needed += purchaseAmount;
|
|
||||||
|
|
||||||
if (needed > mPriorBalance)
|
|
||||||
return tecINSUFFICIENT_FUNDS;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// IOU sale
|
|
||||||
if (TER result = trustTransferAllowed(
|
|
||||||
sb, {account_, *owner}, purchaseAmount.issue(), j);
|
|
||||||
!isTesSuccess(result))
|
|
||||||
{
|
|
||||||
JLOG(j.trace())
|
|
||||||
<< "URIToken::doApply trustTransferAllowed result="
|
|
||||||
<< result;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (STAmount availableFunds{accountFunds(
|
|
||||||
sb, account_, purchaseAmount, fhZERO_IF_FROZEN, j)};
|
|
||||||
purchaseAmount > availableFunds)
|
|
||||||
return tecINSUFFICIENT_FUNDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute the funds transfer, we'll check reserves last
|
|
||||||
if (TER result = accountSend(
|
|
||||||
sb, account_, *owner, purchaseAmount, j, false);
|
|
||||||
!isTesSuccess(result))
|
|
||||||
return result;
|
|
||||||
|
|
||||||
// add token to new owner dir
|
|
||||||
auto const newPage = sb.dirInsert(
|
|
||||||
keylet::ownerDir(account_),
|
|
||||||
*kl,
|
|
||||||
describeOwnerDir(account_));
|
|
||||||
|
|
||||||
JLOG(j_.trace()) << "Adding URIToken to owner directory "
|
|
||||||
<< to_string(kl->key) << ": "
|
|
||||||
<< (newPage ? "success" : "failure");
|
|
||||||
|
|
||||||
if (!newPage)
|
|
||||||
return tecDIR_FULL;
|
|
||||||
|
|
||||||
// remove from current owner directory
|
|
||||||
if (!sb.dirRemove(
|
|
||||||
keylet::ownerDir(*owner),
|
|
||||||
sleU->getFieldU64(sfOwnerNode),
|
|
||||||
kl->key,
|
|
||||||
true))
|
|
||||||
{
|
|
||||||
JLOG(j.fatal())
|
|
||||||
<< "Could not remove URIToken from owner directory";
|
|
||||||
|
|
||||||
return tefBAD_LEDGER;
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjust owner counts
|
|
||||||
adjustOwnerCount(sb, sleOwner, -1, j);
|
|
||||||
adjustOwnerCount(sb, 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);
|
|
||||||
|
|
||||||
// check each side has sufficient balance remaining to cover the
|
|
||||||
// updated ownercounts
|
|
||||||
auto hasSufficientReserve =
|
|
||||||
[&](std::shared_ptr<SLE> const& sle) -> bool {
|
|
||||||
std::uint32_t const uOwnerCount =
|
|
||||||
sle->getFieldU32(sfOwnerCount);
|
|
||||||
return sle->getFieldAmount(sfBalance) >=
|
|
||||||
sb.fees().accountReserve(uOwnerCount);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!hasSufficientReserve(sle))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "URIToken: buyer " << account_
|
|
||||||
<< " has insufficient reserve to buy";
|
|
||||||
return tecINSUFFICIENT_RESERVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should only happen if the owner burned their reserves
|
|
||||||
// below the needed amount via another transactor. If this
|
|
||||||
// happens they should top up their account before selling!
|
|
||||||
if (!hasSufficientReserve(sleOwner))
|
|
||||||
{
|
|
||||||
JLOG(j.warn())
|
|
||||||
<< "URIToken: seller " << *owner
|
|
||||||
<< " has insufficient reserve to allow purchase!";
|
|
||||||
return tecINSUF_RESERVE_SELLER;
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.update(sle);
|
|
||||||
sb.update(sleU);
|
|
||||||
sb.update(sleOwner);
|
|
||||||
sb.apply(ctx_.rawView());
|
sb.apply(ctx_.rawView());
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
@@ -750,10 +1054,9 @@ URIToken::doApply()
|
|||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
initSellerBal = !sleDstLine
|
initSellerBal = !sleDstLine ? purchaseAmount.zeroed()
|
||||||
? purchaseAmount.zeroed()
|
: sellerLow ? ((*sleDstLine)[sfBalance])
|
||||||
: sellerLow ? ((*sleDstLine)[sfBalance])
|
: -((*sleDstLine)[sfBalance]);
|
||||||
: -((*sleDstLine)[sfBalance]);
|
|
||||||
|
|
||||||
finSellerBal = *initSellerBal + *dstAmt;
|
finSellerBal = *initSellerBal + *dstAmt;
|
||||||
}
|
}
|
||||||
@@ -1016,6 +1319,54 @@ URIToken::doApply()
|
|||||||
if (account_ != *owner)
|
if (account_ != *owner)
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
|
|
||||||
|
// Amendment Guard
|
||||||
|
auto const amount = ctx_.tx[sfAmount];
|
||||||
|
auto uRate = getRate(amount, STAmount{1});
|
||||||
|
auto const buyOfferKey = keylet::uritoken_offer(
|
||||||
|
*kl, uRate, amount.getIssuer(), amount.getCurrency());
|
||||||
|
if (sb.exists(buyOfferKey))
|
||||||
|
{
|
||||||
|
auto const buyOfferSle = sb.peek(buyOfferKey);
|
||||||
|
owner = buyOfferSle->getAccountID(sfOwner);
|
||||||
|
STAmount const offerAmount =
|
||||||
|
buyOfferSle->getFieldAmount(sfAmount);
|
||||||
|
sleOwner = sb.peek(keylet::account(*owner));
|
||||||
|
STAmount const fee = ctx_.tx.getFieldAmount(sfFee).xrp();
|
||||||
|
|
||||||
|
auto sleIssuer = sb.peek(keylet::account(*issuer));
|
||||||
|
// Unlock & Transfer the amount (XRP or IOU) in the offer
|
||||||
|
if (auto const ter = offerUnLock(
|
||||||
|
sb,
|
||||||
|
mPriorBalance,
|
||||||
|
offerAmount,
|
||||||
|
*owner, // buyer
|
||||||
|
account_, // seller
|
||||||
|
*issuer, // issuer
|
||||||
|
*kl,
|
||||||
|
sleU,
|
||||||
|
sleOwner, // buyer
|
||||||
|
sle, // seller
|
||||||
|
sleIssuer, // issuer
|
||||||
|
j_);
|
||||||
|
!isTesSuccess(ter))
|
||||||
|
return ter;
|
||||||
|
|
||||||
|
if (!sb.dirRemove(
|
||||||
|
keylet::ownerDir(*owner),
|
||||||
|
buyOfferSle->getFieldU64(sfOwnerNode),
|
||||||
|
buyOfferKey.key,
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
JLOG(j.fatal()) << "Could not remove URITokenOffer from "
|
||||||
|
"owner directory";
|
||||||
|
return tefBAD_LEDGER;
|
||||||
|
}
|
||||||
|
adjustOwnerCount(sb, sleOwner, -1, j);
|
||||||
|
sb.erase(buyOfferSle);
|
||||||
|
sb.apply(ctx_.rawView());
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
auto const txDest = ctx_.tx[~sfDestination];
|
auto const txDest = ctx_.tx[~sfDestination];
|
||||||
|
|
||||||
// update destination where applicable
|
// update destination where applicable
|
||||||
@@ -1024,7 +1375,7 @@ URIToken::doApply()
|
|||||||
else if (dest)
|
else if (dest)
|
||||||
sleU->makeFieldAbsent(sfDestination);
|
sleU->makeFieldAbsent(sfDestination);
|
||||||
|
|
||||||
sleU->setFieldAmount(sfAmount, ctx_.tx[sfAmount]);
|
sleU->setFieldAmount(sfAmount, amount);
|
||||||
|
|
||||||
sb.update(sleU);
|
sb.update(sleU);
|
||||||
sb.apply(ctx_.rawView());
|
sb.apply(ctx_.rawView());
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <ripple/app/ledger/Ledger.h>
|
#include <ripple/app/ledger/Ledger.h>
|
||||||
#include <ripple/app/tx/impl/Transactor.h>
|
#include <ripple/app/tx/impl/Transactor.h>
|
||||||
|
// #include <ripple/app/tx/impl/URITokenUtils.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
#include <ripple/protocol/Indexes.h>
|
#include <ripple/protocol/Indexes.h>
|
||||||
|
|
||||||
|
|||||||
260
src/ripple/app/tx/impl/URITokenUtils.cpp
Normal file
260
src/ripple/app/tx/impl/URITokenUtils.cpp
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/tx/impl/URITokenUtils.h>
|
||||||
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/protocol/Feature.h>
|
||||||
|
#include <ripple/protocol/Indexes.h>
|
||||||
|
#include <ripple/protocol/Quality.h>
|
||||||
|
#include <ripple/protocol/STAccount.h>
|
||||||
|
#include <ripple/protocol/TER.h>
|
||||||
|
#include <ripple/protocol/TxFlags.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace uritoken {
|
||||||
|
|
||||||
|
TER
|
||||||
|
transfer(
|
||||||
|
Sandbox& sb,
|
||||||
|
AccountID const& receiver,
|
||||||
|
AccountID const& sender,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleReciever,
|
||||||
|
std::shared_ptr<SLE> const& sleSender,
|
||||||
|
beast::Journal journal)
|
||||||
|
{
|
||||||
|
// add token to new owner dir
|
||||||
|
auto const newPage = sb.dirInsert(
|
||||||
|
keylet::ownerDir(receiver), kl, describeOwnerDir(receiver));
|
||||||
|
|
||||||
|
JLOG(journal.trace()) << "Adding URIToken to owner directory "
|
||||||
|
<< to_string(kl.key) << ": "
|
||||||
|
<< (newPage ? "success" : "failure");
|
||||||
|
|
||||||
|
if (!newPage)
|
||||||
|
return tecDIR_FULL;
|
||||||
|
|
||||||
|
// remove from current owner directory
|
||||||
|
if (!sb.dirRemove(
|
||||||
|
keylet::ownerDir(sender),
|
||||||
|
sleU->getFieldU64(sfOwnerNode),
|
||||||
|
kl.key,
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
JLOG(journal.fatal())
|
||||||
|
<< "Could not remove URIToken from owner directory";
|
||||||
|
|
||||||
|
return tefBAD_LEDGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust owner counts
|
||||||
|
adjustOwnerCount(sb, sleSender, -1, journal);
|
||||||
|
adjustOwnerCount(sb, sleReciever, 1, journal);
|
||||||
|
|
||||||
|
// 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, receiver);
|
||||||
|
|
||||||
|
// tell the ledger where to find it
|
||||||
|
sleU->setFieldU64(sfOwnerNode, *newPage);
|
||||||
|
|
||||||
|
// check each side has sufficient balance remaining to cover the
|
||||||
|
// updated ownercounts
|
||||||
|
auto hasSufficientReserve = [&](std::shared_ptr<SLE> const& sle) -> bool {
|
||||||
|
std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount);
|
||||||
|
return sle->getFieldAmount(sfBalance) >=
|
||||||
|
sb.fees().accountReserve(uOwnerCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!hasSufficientReserve(sleReciever))
|
||||||
|
{
|
||||||
|
JLOG(journal.trace()) << "URIToken: buyer " << receiver
|
||||||
|
<< " has insufficient reserve to buy";
|
||||||
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should only happen if the owner burned their reserves
|
||||||
|
// below the needed amount via another transactor. If this
|
||||||
|
// happens they should top up their account before selling!
|
||||||
|
if (!hasSufficientReserve(sleSender))
|
||||||
|
{
|
||||||
|
JLOG(journal.warn()) << "URIToken: seller " << sender
|
||||||
|
<< " has insufficient reserve to allow purchase!";
|
||||||
|
return tecINSUF_RESERVE_SELLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.update(sleU);
|
||||||
|
sb.update(sleReciever);
|
||||||
|
sb.update(sleSender);
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
fullSwap(
|
||||||
|
Sandbox& sb,
|
||||||
|
STAmount const& priorBalance,
|
||||||
|
STAmount const& purchaseAmount,
|
||||||
|
STAmount const& saleAmount,
|
||||||
|
STAmount const& fee,
|
||||||
|
AccountID const& receiver,
|
||||||
|
AccountID const& sender,
|
||||||
|
AccountID const& issuer,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleReciever,
|
||||||
|
std::shared_ptr<SLE> const& sleSender,
|
||||||
|
beast::Journal journal)
|
||||||
|
{
|
||||||
|
if (purchaseAmount < saleAmount)
|
||||||
|
return tecINSUFFICIENT_PAYMENT;
|
||||||
|
|
||||||
|
// if it's an xrp sale/purchase then no trustline needed
|
||||||
|
if (purchaseAmount.native())
|
||||||
|
{
|
||||||
|
STAmount needed{sb.fees().accountReserve(
|
||||||
|
sleReciever->getFieldU32(sfOwnerCount) + 1)};
|
||||||
|
|
||||||
|
// STAmount const fee = ctx_.tx.getFieldAmount(sfFee).xrp();
|
||||||
|
|
||||||
|
if (needed + fee < needed)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
needed += fee;
|
||||||
|
|
||||||
|
if (needed + purchaseAmount < needed)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
needed += purchaseAmount;
|
||||||
|
|
||||||
|
if (needed > priorBalance)
|
||||||
|
return tecINSUFFICIENT_FUNDS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// IOU sale
|
||||||
|
if (TER result = trustTransferAllowed(
|
||||||
|
sb, {receiver, sender}, purchaseAmount.issue(), journal);
|
||||||
|
!isTesSuccess(result))
|
||||||
|
{
|
||||||
|
JLOG(journal.trace())
|
||||||
|
<< "URIToken::doApply trustTransferAllowed result=" << result;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (STAmount availableFunds{accountFunds(
|
||||||
|
sb, receiver, purchaseAmount, fhZERO_IF_FROZEN, journal)};
|
||||||
|
purchaseAmount > availableFunds)
|
||||||
|
return tecINSUFFICIENT_FUNDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute the funds transfer, we'll check reserves last
|
||||||
|
if (TER result =
|
||||||
|
accountSend(sb, receiver, sender, purchaseAmount, journal, false);
|
||||||
|
!isTesSuccess(result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (TER result = transfer(
|
||||||
|
sb,
|
||||||
|
receiver,
|
||||||
|
sender,
|
||||||
|
kl,
|
||||||
|
sleU,
|
||||||
|
sleReciever,
|
||||||
|
sleSender,
|
||||||
|
journal);
|
||||||
|
!isTesSuccess(result))
|
||||||
|
{
|
||||||
|
JLOG(journal.fatal())
|
||||||
|
<< "URIToken: transfer failed: " << result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
offerLock(
|
||||||
|
Sandbox& sb,
|
||||||
|
STAmount const& priorBalance,
|
||||||
|
STAmount const& purchaseAmount,
|
||||||
|
AccountID const& receiver,
|
||||||
|
AccountID const& sender,
|
||||||
|
AccountID const& issuer,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleReciever,
|
||||||
|
std::shared_ptr<SLE> const& sleSender,
|
||||||
|
beast::Journal journal)
|
||||||
|
{
|
||||||
|
STAmount deliverAmount = purchaseAmount;
|
||||||
|
STAmount royaltyAmount = STAmount{0};
|
||||||
|
|
||||||
|
// Amendment Guard
|
||||||
|
if (sleU->isFieldPresent(sfRoyaltyRate))
|
||||||
|
{
|
||||||
|
auto const royaltyRate = sleU->getFieldU32(sfRoyaltyRate);
|
||||||
|
royaltyAmount = multiplyRound(
|
||||||
|
deliverAmount, Rate{royaltyRate}, deliverAmount.issue(), true);
|
||||||
|
deliverAmount -= royaltyAmount;
|
||||||
|
STAmount deliverAmount = purchaseAmount;
|
||||||
|
static Rate const parityRate(QUALITY_ONE);
|
||||||
|
auto const royaltyRate = [&]() -> Rate {
|
||||||
|
if (sleU->isFieldPresent(sfRoyaltyRate))
|
||||||
|
return Rate{sleU->getFieldU32(sfRoyaltyRate)};
|
||||||
|
return Rate{QUALITY_ONE};
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (royaltyRate != parityRate)
|
||||||
|
deliverAmount = multiplyRound(
|
||||||
|
deliverAmount, royaltyRate, deliverAmount.issue(), true);
|
||||||
|
royaltyAmount = deliverAmount - purchaseAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isXRP(purchaseAmount))
|
||||||
|
(*sleSender)[sfBalance] = (*sleSender)[sfBalance] - purchaseAmount;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if (!sb.rules().enabled(featureRoyalties))
|
||||||
|
// return temDISABLED;
|
||||||
|
|
||||||
|
TER const result = trustTransferAllowed(sb, {sender, receiver}, purchaseAmount.issue(), journal);
|
||||||
|
if (!isTesSuccess(result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
auto sleLine = sb.peek(keylet::line(sender, purchaseAmount.getIssuer(), purchaseAmount.getCurrency()));
|
||||||
|
if (issuer != sender)
|
||||||
|
{
|
||||||
|
if (!sleLine)
|
||||||
|
return tecNO_LINE;
|
||||||
|
|
||||||
|
TER const result = trustAdjustLockedBalance(sb, sleLine, purchaseAmount, 1, journal, WetRun);
|
||||||
|
if (!isTesSuccess(result))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uritoken
|
||||||
|
} // namespace ripple
|
||||||
75
src/ripple/app/tx/impl/URITokenUtils.h
Normal file
75
src/ripple/app/tx/impl/URITokenUtils.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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_URITOKENUTILS_H_INCLUDED
|
||||||
|
#define RIPPLE_TX_URITOKENUTILS_H_INCLUDED
|
||||||
|
|
||||||
|
#include <ripple/app/tx/impl/Transactor.h>
|
||||||
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/ledger/Sandbox.h>
|
||||||
|
#include <ripple/protocol/TER.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace uritoken {
|
||||||
|
|
||||||
|
TER
|
||||||
|
transfer(
|
||||||
|
Sandbox& sb,
|
||||||
|
AccountID const& receiver,
|
||||||
|
AccountID const& sender,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleReciever,
|
||||||
|
std::shared_ptr<SLE> const& sleSender,
|
||||||
|
beast::Journal journal);
|
||||||
|
|
||||||
|
TER
|
||||||
|
fullSwap(
|
||||||
|
Sandbox& sb,
|
||||||
|
STAmount const& priorBalance,
|
||||||
|
STAmount const& purchaseAmount,
|
||||||
|
STAmount const& saleAmount,
|
||||||
|
STAmount const& fee,
|
||||||
|
AccountID const& receiver,
|
||||||
|
AccountID const& sender,
|
||||||
|
AccountID const& issuer,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleReciever,
|
||||||
|
std::shared_ptr<SLE> const& sleSender,
|
||||||
|
beast::Journal journal);
|
||||||
|
|
||||||
|
TER
|
||||||
|
offerLock(
|
||||||
|
Sandbox& sb,
|
||||||
|
STAmount const& priorBalance,
|
||||||
|
STAmount const& purchaseAmount,
|
||||||
|
AccountID const& receiver,
|
||||||
|
AccountID const& sender,
|
||||||
|
AccountID const& issuer,
|
||||||
|
Keylet const& kl,
|
||||||
|
std::shared_ptr<SLE> const& sleU,
|
||||||
|
std::shared_ptr<SLE> const& sleReciever,
|
||||||
|
std::shared_ptr<SLE> const& sleSender,
|
||||||
|
beast::Journal journal);
|
||||||
|
|
||||||
|
} // namespace uritoken
|
||||||
|
} // namespace ripple
|
||||||
|
|
||||||
|
#endif // RIPPLE_TX_URITOKENUTILS_H_INCLUDED
|
||||||
@@ -443,7 +443,8 @@ struct RunType
|
|||||||
{
|
{
|
||||||
// see:
|
// see:
|
||||||
// http://alumni.media.mit.edu/~rahimi/compile-time-flags/
|
// http://alumni.media.mit.edu/~rahimi/compile-time-flags/
|
||||||
constexpr operator T() const
|
constexpr
|
||||||
|
operator T() const
|
||||||
{
|
{
|
||||||
static_assert(std::is_same<bool, T>::value);
|
static_assert(std::is_same<bool, T>::value);
|
||||||
return V;
|
return V;
|
||||||
@@ -493,6 +494,8 @@ trustAdjustLockedBalance(
|
|||||||
(std::is_same<V, ReadView const>::value &&
|
(std::is_same<V, ReadView const>::value &&
|
||||||
std::is_same<S, std::shared_ptr<SLE const>>::value) ||
|
std::is_same<S, std::shared_ptr<SLE const>>::value) ||
|
||||||
(std::is_same<V, ApplyView>::value &&
|
(std::is_same<V, ApplyView>::value &&
|
||||||
|
std::is_same<S, std::shared_ptr<SLE>>::value) ||
|
||||||
|
(std::is_same<V, Sandbox>::value &&
|
||||||
std::is_same<S, std::shared_ptr<SLE>>::value));
|
std::is_same<S, std::shared_ptr<SLE>>::value));
|
||||||
|
|
||||||
// dry runs are explicit in code, but really the view type determines
|
// dry runs are explicit in code, but really the view type determines
|
||||||
@@ -589,8 +592,10 @@ trustAdjustLockedBalance(
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
|
|
||||||
if constexpr (
|
if constexpr (
|
||||||
std::is_same<V, ApplyView>::value &&
|
(std::is_same<V, ApplyView>::value &&
|
||||||
std::is_same<S, std::shared_ptr<SLE>>::value)
|
std::is_same<S, std::shared_ptr<SLE>>::value) ||
|
||||||
|
(std::is_same<V, Sandbox>::value &&
|
||||||
|
std::is_same<S, std::shared_ptr<SLE>>::value))
|
||||||
{
|
{
|
||||||
if (finalLockedBalance == beast::zero || finalLockCount == 0)
|
if (finalLockedBalance == beast::zero || finalLockCount == 0)
|
||||||
{
|
{
|
||||||
@@ -785,18 +790,25 @@ trustTransferLockedBalance(
|
|||||||
R dryRun)
|
R dryRun)
|
||||||
{
|
{
|
||||||
typedef typename std::conditional<
|
typedef typename std::conditional<
|
||||||
std::is_same<V, ApplyView>::value && !dryRun,
|
(std::is_same<V, ApplyView>::value ||
|
||||||
|
std::is_same<V, Sandbox>::value) &&
|
||||||
|
!dryRun,
|
||||||
std::shared_ptr<SLE>,
|
std::shared_ptr<SLE>,
|
||||||
std::shared_ptr<SLE const>>::type SLEPtr;
|
std::shared_ptr<SLE const>>::type SLEPtr;
|
||||||
|
|
||||||
auto peek = [&](Keylet& k) {
|
auto peek = [&](Keylet& k) {
|
||||||
if constexpr (std::is_same<V, ApplyView>::value && !dryRun)
|
if constexpr (std::is_same<V, ApplyView>::value && !dryRun)
|
||||||
return const_cast<ApplyView&>(view).peek(k);
|
return const_cast<ApplyView&>(view).peek(k);
|
||||||
|
else if constexpr (std::is_same<V, Sandbox>::value && !dryRun)
|
||||||
|
return const_cast<Sandbox&>(view).peek(k);
|
||||||
else
|
else
|
||||||
return view.read(k);
|
return view.read(k);
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(std::is_same<V, ApplyView>::value || dryRun);
|
static_assert(
|
||||||
|
std::is_same<V, ApplyView>::value || std::is_same<V, Sandbox>::value ||
|
||||||
|
dryRun,
|
||||||
|
"trustTransferLockedBalance requires ApplyView or Sandbox");
|
||||||
|
|
||||||
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||||
return tefINTERNAL;
|
return tefINTERNAL;
|
||||||
@@ -1032,7 +1044,9 @@ trustTransferLockedBalance(
|
|||||||
|
|
||||||
if constexpr (!dryRun)
|
if constexpr (!dryRun)
|
||||||
{
|
{
|
||||||
static_assert(std::is_same<V, ApplyView>::value);
|
static_assert(
|
||||||
|
std::is_same<V, ApplyView>::value ||
|
||||||
|
std::is_same<V, Sandbox>::value);
|
||||||
|
|
||||||
// if source account is not issuer
|
// if source account is not issuer
|
||||||
if (!srcIssuer)
|
if (!srcIssuer)
|
||||||
|
|||||||
@@ -297,6 +297,10 @@ import_vlseq(PublicKey const& key) noexcept;
|
|||||||
Keylet
|
Keylet
|
||||||
uritoken(AccountID const& issuer, Blob const& uri);
|
uritoken(AccountID const& issuer, Blob const& uri);
|
||||||
|
|
||||||
|
/** The initial directory page for a specific quality */
|
||||||
|
Keylet
|
||||||
|
uritoken_offer(Keylet const& k, std::uint64_t q, AccountID const& issuer, Currency const& currency) noexcept;
|
||||||
|
|
||||||
} // namespace keylet
|
} // namespace keylet
|
||||||
|
|
||||||
// Everything below is deprecated and should be removed in favor of keylets:
|
// Everything below is deprecated and should be removed in favor of keylets:
|
||||||
|
|||||||
@@ -179,6 +179,13 @@ enum LedgerEntryType : std::uint16_t
|
|||||||
*/
|
*/
|
||||||
ltUNL_REPORT = 0x0052,
|
ltUNL_REPORT = 0x0052,
|
||||||
|
|
||||||
|
/** A ledger object that reports on the active dUNL validators
|
||||||
|
* that were validating for more than 240 of the last 256 ledgers
|
||||||
|
*
|
||||||
|
* \sa keylet::uritoken_offer
|
||||||
|
*/
|
||||||
|
ltURI_TOKEN_OFFER = 0x0056,
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
/** A special type, matching any ledger entry type.
|
/** A special type, matching any ledger entry type.
|
||||||
|
|
||||||
|
|||||||
@@ -370,6 +370,7 @@ extern SF_UINT32 const sfTransferRate;
|
|||||||
extern SF_UINT32 const sfWalletSize;
|
extern SF_UINT32 const sfWalletSize;
|
||||||
extern SF_UINT32 const sfOwnerCount;
|
extern SF_UINT32 const sfOwnerCount;
|
||||||
extern SF_UINT32 const sfDestinationTag;
|
extern SF_UINT32 const sfDestinationTag;
|
||||||
|
extern SF_UINT32 const sfRoyaltyRate;
|
||||||
|
|
||||||
// 32-bit integers (uncommon)
|
// 32-bit integers (uncommon)
|
||||||
extern SF_UINT32 const sfHighQualityIn;
|
extern SF_UINT32 const sfHighQualityIn;
|
||||||
|
|||||||
@@ -182,7 +182,11 @@ constexpr std::uint32_t const tfNFTokenCancelOfferMask = ~(tfUniversal);
|
|||||||
constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversal;
|
constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversal;
|
||||||
|
|
||||||
// URIToken mask
|
// URIToken mask
|
||||||
constexpr std::uint32_t const tfURITokenMintMask = ~(tfUniversal | tfBurnable);
|
enum URITokenMintFlags : uint32_t {
|
||||||
|
// tfBurnable = 0x00000001,
|
||||||
|
tfRoyalty = 0x00000002,
|
||||||
|
};
|
||||||
|
constexpr std::uint32_t const tfURITokenMintMask = ~(tfUniversal | tfBurnable | tfRoyalty);
|
||||||
constexpr std::uint32_t const tfURITokenNonMintMask = ~tfUniversal;
|
constexpr std::uint32_t const tfURITokenNonMintMask = ~tfUniversal;
|
||||||
|
|
||||||
// ClaimReward flags:
|
// ClaimReward flags:
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
|||||||
URI_TOKEN = 'U',
|
URI_TOKEN = 'U',
|
||||||
IMPORT_VLSEQ = 'I',
|
IMPORT_VLSEQ = 'I',
|
||||||
UNL_REPORT = 'R',
|
UNL_REPORT = 'R',
|
||||||
|
URI_TOKEN_OFFER = 'z',
|
||||||
|
|
||||||
// No longer used or supported. Left here to reserve the space
|
// No longer used or supported. Left here to reserve the space
|
||||||
// to avoid accidental reuse.
|
// to avoid accidental reuse.
|
||||||
@@ -443,6 +444,24 @@ uritoken(AccountID const& issuer, Blob const& uri)
|
|||||||
LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})};
|
LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Keylet
|
||||||
|
uritoken_offer(Keylet const& k, std::uint64_t q, AccountID const& issuer, Currency const& currency) noexcept
|
||||||
|
{
|
||||||
|
assert(k.type == ltURI_TOKEN);
|
||||||
|
|
||||||
|
// Indexes are stored in big endian format: they print as hex as stored.
|
||||||
|
// Most significant bytes are first and the least significant bytes
|
||||||
|
// represent adjacent entries. We place the quality, in big endian format,
|
||||||
|
// in the 8 right most bytes; this way, incrementing goes to the next entry
|
||||||
|
// for indexes.
|
||||||
|
uint256 x = k.key;
|
||||||
|
|
||||||
|
// FIXME This is ugly and we can and should do better...
|
||||||
|
((std::uint64_t*)x.end())[-1] = boost::endian::native_to_big(q);
|
||||||
|
|
||||||
|
return {ltURI_TOKEN_OFFER, indexHash(LedgerNameSpace::URI_TOKEN_OFFER, x, issuer, currency)};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace keylet
|
} // namespace keylet
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -359,6 +359,19 @@ LedgerFormats::LedgerFormats()
|
|||||||
{sfDigest, soeOPTIONAL},
|
{sfDigest, soeOPTIONAL},
|
||||||
{sfAmount, soeOPTIONAL},
|
{sfAmount, soeOPTIONAL},
|
||||||
{sfDestination, soeOPTIONAL},
|
{sfDestination, soeOPTIONAL},
|
||||||
|
{sfRoyaltyRate, soeOPTIONAL},
|
||||||
|
{sfPreviousTxnID, soeREQUIRED},
|
||||||
|
{sfPreviousTxnLgrSeq, soeREQUIRED}
|
||||||
|
},
|
||||||
|
commonFields);
|
||||||
|
|
||||||
|
add(jss::URITokenOffer,
|
||||||
|
ltURI_TOKEN_OFFER,
|
||||||
|
{
|
||||||
|
{sfOwner, soeREQUIRED},
|
||||||
|
{sfOwnerNode, soeREQUIRED},
|
||||||
|
{sfURITokenID, soeREQUIRED},
|
||||||
|
{sfAmount, soeREQUIRED},
|
||||||
{sfPreviousTxnID, soeREQUIRED},
|
{sfPreviousTxnID, soeREQUIRED},
|
||||||
{sfPreviousTxnLgrSeq, soeREQUIRED}
|
{sfPreviousTxnLgrSeq, soeREQUIRED}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ CONSTRUCT_TYPED_SFIELD(sfTransferRate, "TransferRate", UINT32,
|
|||||||
CONSTRUCT_TYPED_SFIELD(sfWalletSize, "WalletSize", UINT32, 12);
|
CONSTRUCT_TYPED_SFIELD(sfWalletSize, "WalletSize", UINT32, 12);
|
||||||
CONSTRUCT_TYPED_SFIELD(sfOwnerCount, "OwnerCount", UINT32, 13);
|
CONSTRUCT_TYPED_SFIELD(sfOwnerCount, "OwnerCount", UINT32, 13);
|
||||||
CONSTRUCT_TYPED_SFIELD(sfDestinationTag, "DestinationTag", UINT32, 14);
|
CONSTRUCT_TYPED_SFIELD(sfDestinationTag, "DestinationTag", UINT32, 14);
|
||||||
|
CONSTRUCT_TYPED_SFIELD(sfRoyaltyRate, "RoyaltyRate", UINT32, 15);
|
||||||
|
|
||||||
// 32-bit integers (uncommon)
|
// 32-bit integers (uncommon)
|
||||||
CONSTRUCT_TYPED_SFIELD(sfHighQualityIn, "HighQualityIn", UINT32, 16);
|
CONSTRUCT_TYPED_SFIELD(sfHighQualityIn, "HighQualityIn", UINT32, 16);
|
||||||
|
|||||||
@@ -419,6 +419,7 @@ TxFormats::TxFormats()
|
|||||||
{sfAmount, soeOPTIONAL},
|
{sfAmount, soeOPTIONAL},
|
||||||
{sfDestination, soeOPTIONAL},
|
{sfDestination, soeOPTIONAL},
|
||||||
{sfTicketSequence, soeOPTIONAL},
|
{sfTicketSequence, soeOPTIONAL},
|
||||||
|
{sfRoyaltyRate, soeOPTIONAL},
|
||||||
},
|
},
|
||||||
commonFields);
|
commonFields);
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ JSS(TransactionType); // in: TransactionSign.
|
|||||||
JSS(TransferRate); // in: TransferRate.
|
JSS(TransferRate); // in: TransferRate.
|
||||||
JSS(TrustSet); // transaction type.
|
JSS(TrustSet); // transaction type.
|
||||||
JSS(URIToken); // out: LedgerEntry
|
JSS(URIToken); // out: LedgerEntry
|
||||||
|
JSS(URITokenOffer); // out: LedgerEntry
|
||||||
JSS(URITokenMint); // tx type
|
JSS(URITokenMint); // tx type
|
||||||
JSS(URITokenBurn); // tx type
|
JSS(URITokenBurn); // tx type
|
||||||
JSS(URITokenBuy); // tx type
|
JSS(URITokenBuy); // tx type
|
||||||
|
|||||||
@@ -483,10 +483,10 @@ struct URIToken_test : public beast::unit_test::suite
|
|||||||
// preclaim
|
// preclaim
|
||||||
|
|
||||||
// tecNO_PERMISSION - not for sale
|
// tecNO_PERMISSION - not for sale
|
||||||
env(uritoken::buy(bob, hexid),
|
// env(uritoken::buy(bob, hexid),
|
||||||
uritoken::amt(USD(10)),
|
// uritoken::amt(USD(10)),
|
||||||
ter(tecNO_PERMISSION));
|
// ter(tecNO_PERMISSION));
|
||||||
env.close();
|
// env.close();
|
||||||
|
|
||||||
// set sell
|
// set sell
|
||||||
env(uritoken::sell(alice, hexid),
|
env(uritoken::sell(alice, hexid),
|
||||||
@@ -2544,31 +2544,138 @@ struct URIToken_test : public beast::unit_test::suite
|
|||||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static STAmount
|
||||||
|
lockedAmount(
|
||||||
|
jtx::Env const& env,
|
||||||
|
jtx::Account const& account,
|
||||||
|
jtx::Account const& gw,
|
||||||
|
jtx::IOU const& iou)
|
||||||
|
{
|
||||||
|
auto const sle = env.le(keylet::line(account, gw, iou.currency));
|
||||||
|
if (sle->isFieldPresent(sfLockedBalance))
|
||||||
|
return (*sle)[sfLockedBalance];
|
||||||
|
return STAmount(iou, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testRoyalty(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("royalty");
|
||||||
|
using namespace jtx;
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
auto const alice = Account("alice");
|
||||||
|
auto const bob = Account("bob");
|
||||||
|
auto const minter = Account("minter");
|
||||||
|
auto const gw = Account{"gateway"};
|
||||||
|
auto const USD = gw["USD"];
|
||||||
|
|
||||||
|
// setup env
|
||||||
|
env.fund(XRP(1000), alice, bob, minter, gw);
|
||||||
|
env.trust(USD(100000), alice, bob, minter);
|
||||||
|
env.close();
|
||||||
|
env(pay(gw, alice, USD(1000)));
|
||||||
|
env(pay(gw, bob, USD(1000)));
|
||||||
|
env(pay(gw, minter, USD(1000)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
auto const feeDrops = env.current()->fees().base;
|
||||||
|
std::string const uri(maxTokenURILength, '?');
|
||||||
|
auto const tid = uritoken::tokenid(minter, uri);
|
||||||
|
std::string const hexid{strHex(tid)};
|
||||||
|
|
||||||
|
// Buy Offer Before Sell Offer
|
||||||
|
{
|
||||||
|
// minter mints
|
||||||
|
const auto delta = USD(10);
|
||||||
|
env(uritoken::mint(minter, uri),
|
||||||
|
uritoken::royalty(1.1),
|
||||||
|
ter(tesSUCCESS));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
BEAST_EXPECT(inOwnerDir(*env.current(), minter, tid));
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
|
||||||
|
|
||||||
|
// minter remits to alice
|
||||||
|
env(remit::remit(minter, alice),
|
||||||
|
remit::token_ids({strHex(tid)}),
|
||||||
|
ter(tesSUCCESS));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), minter, tid));
|
||||||
|
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
|
||||||
|
|
||||||
|
auto preMinterUSD = env.balance(minter, USD.issue());
|
||||||
|
auto preAliceUSD = env.balance(alice, USD.issue());
|
||||||
|
auto preBobUSD = env.balance(bob, USD.issue());
|
||||||
|
|
||||||
|
// bob creates buy offer
|
||||||
|
env(uritoken::buy(bob, hexid), uritoken::amt(delta));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), minter, tid));
|
||||||
|
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
env.balance(minter, USD.issue()) == preMinterUSD);
|
||||||
|
BEAST_EXPECT(
|
||||||
|
env.balance(alice, USD.issue()) == preAliceUSD);
|
||||||
|
BEAST_EXPECT(
|
||||||
|
env.balance(bob, USD.issue()) == preBobUSD);
|
||||||
|
BEAST_EXPECT(
|
||||||
|
-lockedAmount(env, bob, gw, USD) == delta);
|
||||||
|
|
||||||
|
// alice sells to bobs buy offer
|
||||||
|
env(uritoken::sell(alice, hexid), uritoken::amt(delta));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), minter, tid));
|
||||||
|
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
|
||||||
|
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
env.balance(minter, USD.issue()) == preMinterUSD + USD(1));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
env.balance(alice, USD.issue()) == preAliceUSD + (delta - USD(1)));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
env.balance(bob, USD.issue()) == preBobUSD - delta);
|
||||||
|
BEAST_EXPECT(
|
||||||
|
lockedAmount(env, bob, gw, USD) == USD(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testWithFeats(FeatureBitset features)
|
testWithFeats(FeatureBitset features)
|
||||||
{
|
{
|
||||||
testEnabled(features);
|
// testEnabled(features);
|
||||||
testMintInvalid(features);
|
// testMintInvalid(features);
|
||||||
testBurnInvalid(features);
|
// testBurnInvalid(features);
|
||||||
testSellInvalid(features);
|
// testSellInvalid(features);
|
||||||
testBuyInvalid(features);
|
// testBuyInvalid(features);
|
||||||
testClearInvalid(features);
|
// testClearInvalid(features);
|
||||||
testMintValid(features);
|
// testMintValid(features);
|
||||||
testBurnValid(features);
|
// testBurnValid(features);
|
||||||
testBuyValid(features);
|
// testBuyValid(features);
|
||||||
testSellValid(features);
|
// testSellValid(features);
|
||||||
testClearValid(features);
|
// testClearValid(features);
|
||||||
testMetaAndOwnership(features);
|
// testMetaAndOwnership(features);
|
||||||
testAccountDelete(features);
|
// testAccountDelete(features);
|
||||||
testTickets(features);
|
// testTickets(features);
|
||||||
testRippleState(features);
|
// testRippleState(features);
|
||||||
testGateway(features);
|
// testGateway(features);
|
||||||
testRequireAuth(features);
|
// testRequireAuth(features);
|
||||||
testFreeze(features);
|
// testFreeze(features);
|
||||||
testTransferRate(features);
|
// testTransferRate(features);
|
||||||
testDisallowXRP(features);
|
// testDisallowXRP(features);
|
||||||
testLimitAmount(features);
|
// testLimitAmount(features);
|
||||||
testURIUTF8(features);
|
// testURIUTF8(features);
|
||||||
|
testRoyalty(features);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -2578,7 +2685,7 @@ public:
|
|||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
auto const sa = supported_amendments();
|
auto const sa = supported_amendments();
|
||||||
testWithFeats(sa);
|
testWithFeats(sa);
|
||||||
testWithFeats(sa - fixXahauV1);
|
// testWithFeats(sa - fixXahauV1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ mint(jtx::Account const& account, std::string const& uri)
|
|||||||
return jv;
|
return jv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
royalty::operator()(Env& env, JTx& jt) const
|
||||||
|
{
|
||||||
|
jt.jv[sfRoyaltyRate.jsonName] = std::uint32_t(1000000000 * royalty_);;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dest::operator()(Env& env, JTx& jt) const
|
dest::operator()(Env& env, JTx& jt) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,21 @@ tokenid(jtx::Account const& account, std::string const& uri);
|
|||||||
Json::Value
|
Json::Value
|
||||||
mint(jtx::Account const& account, std::string const& uri);
|
mint(jtx::Account const& account, std::string const& uri);
|
||||||
|
|
||||||
|
/** Sets the optional RoyaltyRate on a JTx. */
|
||||||
|
class royalty
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
double royalty_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit royalty(double const& royalty) : royalty_(royalty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(Env&, JTx& jtx) const;
|
||||||
|
};
|
||||||
|
|
||||||
/** Sets the optional Destination on a JTx. */
|
/** Sets the optional Destination on a JTx. */
|
||||||
class dest
|
class dest
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user