Skip to content

Commit

Permalink
Introduce NFT support (XLS020)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbougalis authored and manojsdoshi committed Apr 6, 2022
1 parent 525aaec commit 70779f6
Show file tree
Hide file tree
Showing 97 changed files with 10,382 additions and 770 deletions.
11 changes: 11 additions & 0 deletions Builds/CMake/RippledCore.cmake
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

1 comment on commit 70779f6

@intelliot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.