Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce NFT support (XLS-20) #4101

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions Builds/CMake/RippledCore.cmake
Expand Up @@ -420,6 +420,11 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/DepositPreauth.cpp
src/ripple/app/tx/impl/Escrow.cpp
src/ripple/app/tx/impl/InvariantCheck.cpp
src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp
src/ripple/app/tx/impl/NFTokenBurn.cpp
src/ripple/app/tx/impl/NFTokenCancelOffer.cpp
src/ripple/app/tx/impl/NFTokenCreateOffer.cpp
src/ripple/app/tx/impl/NFTokenMint.cpp
src/ripple/app/tx/impl/OfferStream.cpp
src/ripple/app/tx/impl/PayChan.cpp
src/ripple/app/tx/impl/Payment.cpp
Expand All @@ -432,6 +437,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/Transactor.cpp
src/ripple/app/tx/impl/apply.cpp
src/ripple/app/tx/impl/applySteps.cpp
src/ripple/app/tx/impl/details/NFTokenUtils.cpp
#[===============================[
main sources:
subdir: basics (partial)
Expand Down Expand Up @@ -593,6 +599,7 @@ target_sources (rippled PRIVATE
src/ripple/rpc/handlers/LogLevel.cpp
src/ripple/rpc/handlers/LogRotate.cpp
src/ripple/rpc/handlers/Manifest.cpp
src/ripple/rpc/handlers/NFTOffers.cpp
src/ripple/rpc/handlers/NodeToShard.cpp
src/ripple/rpc/handlers/NoRippleCheck.cpp
src/ripple/rpc/handlers/OwnerInfo.cpp
Expand Down Expand Up @@ -687,6 +694,9 @@ if (tests)
src/test/app/LoadFeeTrack_test.cpp
src/test/app/Manifest_test.cpp
src/test/app/MultiSign_test.cpp
src/test/app/NFToken_test.cpp
src/test/app/NFTokenBurn_test.cpp
src/test/app/NFTokenDir_test.cpp
src/test/app/OfferStream_test.cpp
src/test/app/Offer_test.cpp
src/test/app/OversizeMeta_test.cpp
Expand Down Expand Up @@ -836,6 +846,7 @@ if (tests)
src/test/jtx/impl/sig.cpp
src/test/jtx/impl/tag.cpp
src/test/jtx/impl/ticket.cpp
src/test/jtx/impl/token.cpp
src/test/jtx/impl/trust.cpp
src/test/jtx/impl/txflags.cpp
src/test/jtx/impl/utility.cpp
Expand Down
1 change: 1 addition & 0 deletions src/ripple/app/misc/NetworkOPs.cpp
Expand Up @@ -1178,6 +1178,7 @@ NetworkOPsImp::processTransaction(
if ((newFlags & SF_BAD) != 0)
{
// cached bad
JLOG(m_journal.warn()) << transaction->getID() << ": cached bad!\n";
transaction->setStatus(INVALID);
transaction->setResult(temBAD_SIGNATURE);
return;
Expand Down
3 changes: 1 addition & 2 deletions src/ripple/app/tx/impl/CancelOffer.cpp
Expand Up @@ -27,8 +27,7 @@ namespace ripple {
NotTEC
CancelOffer::preflight(PreflightContext const& ctx)
{
auto const ret = preflight1(ctx);
if (!isTesSuccess(ret))
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

auto const uTxFlags = ctx.tx.getFlags();
Expand Down
19 changes: 5 additions & 14 deletions src/ripple/app/tx/impl/CashCheck.cpp
Expand Up @@ -125,22 +125,13 @@ CashCheck::preclaim(PreclaimContext const& ctx)
return tecDST_TAG_NEEDED;
}
}

if (hasExpired(ctx.view, sleCheck->at(~sfExpiration)))
{
using duration = NetClock::duration;
using timepoint = NetClock::time_point;
auto const optExpiry = sleCheck->at(~sfExpiration);

// Expiration is defined in terms of the close time of the parent
// ledger, because we definitively know the time that it closed but
// we do not know the closing time of the ledger that is under
// construction.
if (optExpiry &&
(ctx.view.parentCloseTime() >= timepoint{duration{*optExpiry}}))
{
JLOG(ctx.j.warn()) << "Cashing a check that has already expired.";
return tecEXPIRED;
}
JLOG(ctx.j.warn()) << "Cashing a check that has already expired.";
return tecEXPIRED;
}

{
// Preflight verified exactly one of Amount or DeliverMin is present.
// Make sure the requested amount is reasonable.
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/Change.cpp
Expand Up @@ -180,7 +180,7 @@ Change::applyAmendment()
// This amendment now has a majority
newMajorities.push_back(STObject(sfMajority));
auto& entry = newMajorities.back();
entry.emplace_back(STHash256(sfAmendment, amendment));
entry.emplace_back(STUInt256(sfAmendment, amendment));
entry.emplace_back(STUInt32(
sfCloseTime, view().parentCloseTime().time_since_epoch().count()));

Expand Down
17 changes: 3 additions & 14 deletions src/ripple/app/tx/impl/CreateCheck.cpp
Expand Up @@ -146,21 +146,10 @@ CreateCheck::preclaim(PreclaimContext const& ctx)
}
}
}
if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
{
using duration = NetClock::duration;
using timepoint = NetClock::time_point;
auto const optExpiry = ctx.tx[~sfExpiration];

// Expiration is defined in terms of the close time of the parent
// ledger, because we definitively know the time that it closed but
// we do not know the closing time of the ledger that is under
// construction.
if (optExpiry &&
(ctx.view.parentCloseTime() >= timepoint{duration{*optExpiry}}))
{
JLOG(ctx.j.warn()) << "Creating a check that has already expired.";
return tecEXPIRED;
}
JLOG(ctx.j.warn()) << "Creating a check that has already expired.";
return tecEXPIRED;
}
return tesSUCCESS;
}
Expand Down
19 changes: 3 additions & 16 deletions src/ripple/app/tx/impl/CreateOffer.cpp
Expand Up @@ -42,8 +42,7 @@ CreateOffer::makeTxConsequences(PreflightContext const& ctx)
NotTEC
CreateOffer::preflight(PreflightContext const& ctx)
{
auto const ret = preflight1(ctx);
if (!isTesSuccess(ret))
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

auto& tx = ctx.tx;
Expand Down Expand Up @@ -173,14 +172,7 @@ CreateOffer::preclaim(PreclaimContext const& ctx)
return temBAD_SEQUENCE;
}

using d = NetClock::duration;
using tp = NetClock::time_point;
auto const expiration = ctx.tx[~sfExpiration];

// Expiration is defined in terms of the close time of the parent ledger,
// because we definitively know the time that it closed but we do not
// know the closing time of the ledger that is under construction.
if (expiration && (ctx.view.parentCloseTime() >= tp{d{*expiration}}))
if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
{
// Note that this will get checked again in applyGuts, but it saves
// us a call to checkAcceptAsset and possible false negative.
Expand Down Expand Up @@ -951,13 +943,8 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
}

auto const expiration = ctx_.tx[~sfExpiration];
using d = NetClock::duration;
using tp = NetClock::time_point;

// Expiration is defined in terms of the close time of the parent ledger,
// because we definitively know the time that it closed but we do not
// know the closing time of the ledger that is under construction.
if (expiration && (sb.parentCloseTime() >= tp{d{*expiration}}))
if (hasExpired(sb, expiration))
{
// If the offer has expired, the transaction has successfully
// done nothing, so short circuit from here.
Expand Down
46 changes: 41 additions & 5 deletions src/ripple/app/tx/impl/DeleteAccount.cpp
Expand Up @@ -20,12 +20,14 @@
#include <ripple/app/tx/impl/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/SetSignerList.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/basics/FeeUnits.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/mulDiv.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>

Expand All @@ -40,9 +42,7 @@ DeleteAccount::preflight(PreflightContext const& ctx)
if (ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

auto const ret = preflight1(ctx);

if (!isTesSuccess(ret))
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
Expand Down Expand Up @@ -127,6 +127,21 @@ removeDepositPreauthFromLedger(
return DepositPreauth::removeFromLedger(app, view, delIndex, j);
}

TER
removeNFTokenOfferFromLedger(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal)
{
if (!nft::deleteTokenOffer(view, sleDel))
return tefBAD_LEDGER;

return tesSUCCESS;
}

// Return nullptr if the LedgerEntryType represents an obligation that can't
// be deleted. Otherwise return the pointer to the function that can delete
// the non-obligation
Expand All @@ -143,6 +158,8 @@ nonObligationDeleter(LedgerEntryType t)
return removeTicketFromLedger;
case ltDEPOSIT_PREAUTH:
return removeDepositPreauthFromLedger;
case ltNFTOKEN_OFFER:
return removeNFTokenOfferFromLedger;
default:
return nullptr;
}
Expand Down Expand Up @@ -177,6 +194,25 @@ DeleteAccount::preclaim(PreclaimContext const& ctx)
if (!sleAccount)
return terNO_ACCOUNT;

if (ctx.view.rules().enabled(featureNonFungibleTokensV1))
{
// If an issuer has any issued NFTs resident in the ledger then it
// cannot be deleted.
if ((*sleAccount)[~sfMintedNFTokens] !=
(*sleAccount)[~sfBurnedNFTokens])
return tecHAS_OBLIGATIONS;

// If the account owns any NFTs it cannot be deleted.
Keylet const first = keylet::nftpage_min(account);
Keylet const last = keylet::nftpage_max(account);

auto const cp = ctx.view.read(Keylet(
ltNFTOKEN_PAGE,
ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
if (cp)
return tecHAS_OBLIGATIONS;
}

// We don't allow an account to be deleted if its sequence number
// is within 256 of the current ledger. This prevents replay of old
// transactions if this account is resurrected after it is deleted.
Expand All @@ -197,10 +233,10 @@ DeleteAccount::preclaim(PreclaimContext const& ctx)
unsigned int uDirEntry{0};
uint256 dirEntry{beast::zero};

// Account has no directory at all. This _should_ have been caught
// by the dirIsEmpty() check earlier, but it's okay to catch it here.
if (!cdirFirst(
ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
// Account has no directory at all. This _should_ have been caught
// by the dirIsEmpty() check earlier, but it's okay to catch it here.
return tesSUCCESS;

std::int32_t deletableDirEntryCount{0};
Expand Down
8 changes: 0 additions & 8 deletions src/ripple/app/tx/impl/DeleteAccount.h
Expand Up @@ -31,14 +31,6 @@ class DeleteAccount : public Transactor
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};

// Set a reasonable upper limit on the number of deletable directory
// entries an account may have before we decide the account can't be
// deleted.
//
// A limit is useful because if we go much past this limit the
// transaction will fail anyway due to too much metadata (tecOVERSIZE).
static constexpr std::int32_t maxDeletableDirEntries{1000};

explicit DeleteAccount(ApplyContext& ctx) : Transactor(ctx)
{
}
Expand Down
3 changes: 1 addition & 2 deletions src/ripple/app/tx/impl/DepositPreauth.cpp
Expand Up @@ -33,8 +33,7 @@ DepositPreauth::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureDepositPreauth))
return temDISABLED;

auto const ret = preflight1(ctx);
if (!isTesSuccess(ret))
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

auto& tx = ctx.tx;
Expand Down
13 changes: 4 additions & 9 deletions src/ripple/app/tx/impl/Escrow.cpp
Expand Up @@ -102,8 +102,7 @@ EscrowCreate::preflight(PreflightContext const& ctx)
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

auto const ret = preflight1(ctx);
if (!isTesSuccess(ret))
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

if (!isXRP(ctx.tx[sfAmount]))
Expand Down Expand Up @@ -298,11 +297,8 @@ EscrowFinish::preflight(PreflightContext const& ctx)
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

{
auto const ret = preflight1(ctx);
if (!isTesSuccess(ret))
return ret;
}
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

auto const cb = ctx.tx[~sfCondition];
auto const fb = ctx.tx[~sfFulfillment];
Expand Down Expand Up @@ -511,8 +507,7 @@ EscrowCancel::preflight(PreflightContext const& ctx)
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

auto const ret = preflight1(ctx);
if (!isTesSuccess(ret))
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

return preflight2(ctx);
Expand Down