mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-04 10:45:50 +00:00
Merge master (1.9.4) into develop (1.10.0-b2)
This commit is contained in:
@@ -13,7 +13,7 @@ if(reporting)
|
||||
ExternalProject_Add(postgres_src
|
||||
PREFIX ${nih_cache_path}
|
||||
GIT_REPOSITORY https://github.com/postgres/postgres.git
|
||||
GIT_TAG master
|
||||
GIT_TAG REL_14_5
|
||||
CONFIGURE_COMMAND ./configure --without-readline > /dev/null
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E env --unset=MAKELEVEL make
|
||||
UPDATE_COMMAND ""
|
||||
|
||||
@@ -11,7 +11,7 @@ if(reporting)
|
||||
ExternalProject_Add(zlib_src
|
||||
PREFIX ${nih_cache_path}
|
||||
GIT_REPOSITORY https://github.com/madler/zlib.git
|
||||
GIT_TAG master
|
||||
GIT_TAG v1.2.12
|
||||
INSTALL_COMMAND ""
|
||||
BUILD_BYPRODUCTS <BINARY_DIR>/${ep_lib_prefix}z.a
|
||||
LOG_BUILD TRUE
|
||||
@@ -45,7 +45,7 @@ if(reporting)
|
||||
ExternalProject_Add(krb5_src
|
||||
PREFIX ${nih_cache_path}
|
||||
GIT_REPOSITORY https://github.com/krb5/krb5.git
|
||||
GIT_TAG master
|
||||
GIT_TAG krb5-1.20-final
|
||||
UPDATE_COMMAND ""
|
||||
CONFIGURE_COMMAND autoreconf src && CFLAGS=-fcommon ./src/configure --enable-static --disable-shared > /dev/null
|
||||
BUILD_IN_SOURCE 1
|
||||
@@ -80,7 +80,7 @@ if(reporting)
|
||||
ExternalProject_Add(libuv_src
|
||||
PREFIX ${nih_cache_path}
|
||||
GIT_REPOSITORY https://github.com/libuv/libuv.git
|
||||
GIT_TAG v1.x
|
||||
GIT_TAG v1.44.2
|
||||
INSTALL_COMMAND ""
|
||||
BUILD_BYPRODUCTS <BINARY_DIR>/${ep_lib_prefix}uv_a.a
|
||||
LOG_BUILD TRUE
|
||||
@@ -106,7 +106,7 @@ if(reporting)
|
||||
ExternalProject_Add(cassandra_src
|
||||
PREFIX ${nih_cache_path}
|
||||
GIT_REPOSITORY https://github.com/datastax/cpp-driver.git
|
||||
GIT_TAG master
|
||||
GIT_TAG 2.16.2
|
||||
CMAKE_ARGS
|
||||
-DLIBUV_ROOT_DIR=${BINARY_DIR}
|
||||
-DLIBUV_LIBARY=${BINARY_DIR}/libuv_a.a
|
||||
|
||||
@@ -7,6 +7,50 @@ This document contains the release notes for `rippled`, the reference server imp
|
||||
|
||||
Have new ideas? Need help with setting up your node? Come visit us [here](https://github.com/xrplf/rippled/issues/new/choose)
|
||||
|
||||
# Introducing XRP Ledger version 1.9.4
|
||||
|
||||
Version 1.9.4 of `rippled`, the reference implementation of the XRP Ledger protocol is now available. This release introduces an amendment that removes the ability for an NFT issuer to indicate that trust lines should be automatically created for royalty payments from secondary sales of NFTs, in response to a bug report that indicated how this functionality could be abused to mount a denial of service attack against the issuer.
|
||||
|
||||
## Action Required
|
||||
|
||||
This release introduces a new amendment to the XRP Ledger protocol, **`fixRemoveNFTokenAutoTrustLine`** to mitigate a potential denial-of-service attack against NFT issuers that minted NFTs and allowed secondary trading of those NFTs to create trust lines for any asset.
|
||||
|
||||
This amendment is open for voting according to the XRP Ledger's [amendment process](https://xrpl.org/amendments.html), which enables protocol changes following two weeks of >80% support from trusted validators.
|
||||
|
||||
If you operate an XRP Ledger server, then you should upgrade to version 1.9.4 within two weeks, to ensure service continuity. The exact time that protocol changes take effect depends on the voting decisions of the decentralized network.
|
||||
|
||||
For more information about NFTs on the XRP Ledger, see [NFT Conceptual Overview](https://xrpl.org/nft-conceptual-overview.html).
|
||||
|
||||
|
||||
## Install / Upgrade
|
||||
|
||||
On supported platforms, see the [instructions on installing or updating `rippled`](https://xrpl.org/install-rippled.html).
|
||||
|
||||
## Changelog
|
||||
|
||||
## Contributions
|
||||
|
||||
The primary change in this release is the following bug fix:
|
||||
|
||||
- **Introduce fixRemoveNFTokenAutoTrustLine amendment**: Introduces the `fixRemoveNFTokenAutoTrustLine` amendment, which disables the `tfTrustLine` flag, which a malicious attacker could exploit to mount denial-of-service attacks against NFT issuers that specified the flag on their NFTs. ([#4301](https://github.com/XRPLF/rippled/4301))
|
||||
|
||||
|
||||
### GitHub
|
||||
|
||||
The public source code repository for `rippled` is hosted on GitHub at <https://github.com/XRPLF/rippled>.
|
||||
|
||||
We welcome all contributions and invite everyone to join the community of XRP Ledger developers and help us build the Internet of Value.
|
||||
|
||||
### Credits
|
||||
|
||||
The following people contributed directly to this release:
|
||||
|
||||
- Scott Schurr <scott@ripple.com>
|
||||
- Howard Hinnant <howard@ripple.com>
|
||||
- Scott Determan <scott.determan@yahoo.com>
|
||||
- Ikko Ashimine <eltociear@gmail.com>
|
||||
|
||||
|
||||
# Introducing XRP Ledger version 1.9.3
|
||||
|
||||
Version 1.9.3 of `rippled`, the reference server implementation of the XRP Ledger protocol is now available. This release corrects minor technical flaws with the code that loads configured amendment votes after a startup and the copy constructor of `PublicKey`.
|
||||
@@ -19,7 +63,7 @@ On supported platforms, see the [instructions on installing or updating `rippled
|
||||
|
||||
## Contributions
|
||||
|
||||
This releases contains the following bug fixes:
|
||||
This release contains the following bug fixes:
|
||||
|
||||
- **Change by-value to by-reference to persist vote**: A minor technical flaw, caused by use of a copy instead of a reference, resulted in operator-configured "yes" votes to not be properly loaded after a restart. ([#4256](https://github.com/XRPLF/rippled/pull/4256))
|
||||
- **Properly handle self-assignment of PublicKey**: The `PublicKey` copy assignment operator mishandled the case where a `PublicKey` would be assigned to itself, and could result in undefined behavior.
|
||||
|
||||
@@ -110,7 +110,7 @@ std::size_t
|
||||
getRows(soci::session& session, TableType type);
|
||||
|
||||
/**
|
||||
* @brief getRowsMinMax Returns minumum ledger sequence,
|
||||
* @brief getRowsMinMax Returns minimum ledger sequence,
|
||||
* maximum ledger sequence and total number of rows in given table.
|
||||
* @param session Session with database.
|
||||
* @param type Table ID for which the result is returned.
|
||||
|
||||
@@ -40,7 +40,23 @@ NFTokenMint::preflight(PreflightContext const& ctx)
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (ctx.tx.getFlags() & tfNFTokenMintMask)
|
||||
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
|
||||
// accounts allowed a TrustLine to be added to the issuer of that token
|
||||
// without explicit permission from that issuer. This was enabled by
|
||||
// minting the NFToken with the tfTrustLine flag set.
|
||||
//
|
||||
// That capability could be used to attack the NFToken issuer. It
|
||||
// would be possible for two accounts to trade the NFToken back and forth
|
||||
// building up any number of TrustLines on the issuer, increasing the
|
||||
// issuer's reserve without bound.
|
||||
//
|
||||
// The fixRemoveNFTokenAutoTrustLine amendment disables minting with the
|
||||
// tfTrustLine flag as a way to prevent the attack. But until the
|
||||
// amendment passes we still need to keep the old behavior available.
|
||||
std::uint32_t const NFTokenMintMask =
|
||||
ctx.rules.enabled(fixRemoveNFTokenAutoTrustLine) ? tfNFTokenMintMask
|
||||
: tfNFTokenMintOldMask;
|
||||
if (ctx.tx.getFlags() & NFTokenMintMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (auto const f = ctx.tx[~sfTransferFee])
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
void
|
||||
|
||||
@@ -338,6 +338,7 @@ extern uint256 const fixNFTokenDirV1;
|
||||
extern uint256 const fixNFTokenNegOffer;
|
||||
extern uint256 const featureNonFungibleTokensV1_1;
|
||||
extern uint256 const fixTrustLinesToSelf;
|
||||
extern uint256 const fixRemoveNFTokenAutoTrustLine;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -120,9 +120,25 @@ constexpr std::uint32_t const tfOnlyXRP = 0x00000002;
|
||||
constexpr std::uint32_t const tfTrustLine = 0x00000004;
|
||||
constexpr std::uint32_t const tfTransferable = 0x00000008;
|
||||
|
||||
constexpr std::uint32_t const tfNFTokenMintMask =
|
||||
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
|
||||
// accounts allowed a TrustLine to be added to the issuer of that token
|
||||
// without explicit permission from that issuer. This was enabled by
|
||||
// minting the NFToken with the tfTrustLine flag set.
|
||||
//
|
||||
// That capability could be used to attack the NFToken issuer. It
|
||||
// would be possible for two accounts to trade the NFToken back and forth
|
||||
// building up any number of TrustLines on the issuer, increasing the
|
||||
// issuer's reserve without bound.
|
||||
//
|
||||
// The fixRemoveNFTokenAutoTrustLine amendment disables minting with the
|
||||
// tfTrustLine flag as a way to prevent the attack. But until the
|
||||
// amendment passes we still need to keep the old behavior available.
|
||||
constexpr std::uint32_t const tfNFTokenMintOldMask =
|
||||
~(tfUniversal | tfBurnable | tfOnlyXRP | tfTrustLine | tfTransferable);
|
||||
|
||||
constexpr std::uint32_t const tfNFTokenMintMask =
|
||||
~(tfUniversal | tfBurnable | tfOnlyXRP | tfTransferable);
|
||||
|
||||
// NFTokenCreateOffer flags:
|
||||
constexpr std::uint32_t const tfSellNFToken = 0x00000001;
|
||||
constexpr std::uint32_t const tfNFTokenCreateOfferMask =
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace BuildInfo {
|
||||
// and follow the format described at http://semver.org/
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
char const* const versionString = "1.10.0-b1"
|
||||
char const* const versionString = "1.9.4"
|
||||
// clang-format on
|
||||
|
||||
#if defined(DEBUG) || defined(SANITIZER)
|
||||
|
||||
@@ -448,6 +448,7 @@ REGISTER_FIX (fixNFTokenDirV1, Supported::yes, DefaultVote::no)
|
||||
REGISTER_FIX (fixNFTokenNegOffer, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FEATURE(NonFungibleTokensV1_1, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FIX (fixTrustLinesToSelf, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FIX (fixRemoveNFTokenAutoTrustLine, 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.
|
||||
|
||||
@@ -1574,7 +1574,6 @@ class NFToken_test : public beast::unit_test::suite
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const alice{"alice"};
|
||||
Account const becky{"becky"};
|
||||
Account const cheri{"cheri"};
|
||||
@@ -1583,155 +1582,179 @@ class NFToken_test : public beast::unit_test::suite
|
||||
IOU const gwCAD(gw["CAD"]);
|
||||
IOU const gwEUR(gw["EUR"]);
|
||||
|
||||
env.fund(XRP(1000), alice, becky, cheri, gw);
|
||||
env.close();
|
||||
|
||||
// Set trust lines so becky and cheri can use gw's currency.
|
||||
env(trust(becky, gwAUD(1000)));
|
||||
env(trust(cheri, gwAUD(1000)));
|
||||
env(trust(becky, gwCAD(1000)));
|
||||
env(trust(cheri, gwCAD(1000)));
|
||||
env(trust(becky, gwEUR(1000)));
|
||||
env(trust(cheri, gwEUR(1000)));
|
||||
env.close();
|
||||
env(pay(gw, becky, gwAUD(500)));
|
||||
env(pay(gw, becky, gwCAD(500)));
|
||||
env(pay(gw, becky, gwEUR(500)));
|
||||
env(pay(gw, cheri, gwAUD(500)));
|
||||
env(pay(gw, cheri, gwCAD(500)));
|
||||
env.close();
|
||||
|
||||
// An nft without flagCreateTrustLines but with a non-zero transfer
|
||||
// fee will not allow creating offers that use IOUs for payment.
|
||||
for (std::uint32_t xferFee : {0, 1})
|
||||
// The behavior of this test changes dramatically based on the
|
||||
// presence (or absence) of the fixRemoveNFTokenAutoTrustLine
|
||||
// amendment. So we test both cases here.
|
||||
for (auto const& tweakedFeatures :
|
||||
{features - fixRemoveNFTokenAutoTrustLine,
|
||||
features | fixRemoveNFTokenAutoTrustLine})
|
||||
{
|
||||
uint256 const nftNoAutoTrustID{
|
||||
token::getNextID(env, alice, 0u, tfTransferable, xferFee)};
|
||||
env(token::mint(alice, 0u),
|
||||
token::xferFee(xferFee),
|
||||
txflags(tfTransferable));
|
||||
Env env{*this, tweakedFeatures};
|
||||
env.fund(XRP(1000), alice, becky, cheri, gw);
|
||||
env.close();
|
||||
|
||||
// becky buys the nft for 1 drop.
|
||||
uint256 const beckyBuyOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
|
||||
token::owner(alice));
|
||||
// Set trust lines so becky and cheri can use gw's currency.
|
||||
env(trust(becky, gwAUD(1000)));
|
||||
env(trust(cheri, gwAUD(1000)));
|
||||
env(trust(becky, gwCAD(1000)));
|
||||
env(trust(cheri, gwCAD(1000)));
|
||||
env(trust(becky, gwEUR(1000)));
|
||||
env(trust(cheri, gwEUR(1000)));
|
||||
env.close();
|
||||
env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
|
||||
env(pay(gw, becky, gwAUD(500)));
|
||||
env(pay(gw, becky, gwCAD(500)));
|
||||
env(pay(gw, becky, gwEUR(500)));
|
||||
env(pay(gw, cheri, gwAUD(500)));
|
||||
env(pay(gw, cheri, gwCAD(500)));
|
||||
env.close();
|
||||
|
||||
// becky attempts to sell the nft for AUD.
|
||||
TER const createOfferTER =
|
||||
xferFee ? TER(tecNO_LINE) : TER(tesSUCCESS);
|
||||
uint256 const beckyOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
|
||||
txflags(tfSellNFToken),
|
||||
ter(createOfferTER));
|
||||
env.close();
|
||||
// An nft without flagCreateTrustLines but with a non-zero transfer
|
||||
// fee will not allow creating offers that use IOUs for payment.
|
||||
for (std::uint32_t xferFee : {0, 1})
|
||||
{
|
||||
uint256 const nftNoAutoTrustID{
|
||||
token::getNextID(env, alice, 0u, tfTransferable, xferFee)};
|
||||
env(token::mint(alice, 0u),
|
||||
token::xferFee(xferFee),
|
||||
txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
// cheri offers to buy the nft for CAD.
|
||||
uint256 const cheriOfferIndex =
|
||||
keylet::nftoffer(cheri, env.seq(cheri)).key;
|
||||
env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
|
||||
token::owner(becky),
|
||||
ter(createOfferTER));
|
||||
env.close();
|
||||
// becky buys the nft for 1 drop.
|
||||
uint256 const beckyBuyOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
|
||||
token::owner(alice));
|
||||
env.close();
|
||||
env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
|
||||
env.close();
|
||||
|
||||
// To keep things tidy, cancel the offers.
|
||||
env(token::cancelOffer(becky, {beckyOfferIndex}));
|
||||
env(token::cancelOffer(cheri, {cheriOfferIndex}));
|
||||
env.close();
|
||||
}
|
||||
// An nft with flagCreateTrustLines but with a non-zero transfer
|
||||
// fee allows transfers using IOUs for payment.
|
||||
{
|
||||
std::uint16_t transferFee = 10000; // 10%
|
||||
// becky attempts to sell the nft for AUD.
|
||||
TER const createOfferTER =
|
||||
xferFee ? TER(tecNO_LINE) : TER(tesSUCCESS);
|
||||
uint256 const beckyOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
|
||||
txflags(tfSellNFToken),
|
||||
ter(createOfferTER));
|
||||
env.close();
|
||||
|
||||
uint256 const nftAutoTrustID{token::getNextID(
|
||||
env, alice, 0u, tfTransferable | tfTrustLine, transferFee)};
|
||||
env(token::mint(alice, 0u),
|
||||
token::xferFee(transferFee),
|
||||
txflags(tfTransferable | tfTrustLine));
|
||||
env.close();
|
||||
// cheri offers to buy the nft for CAD.
|
||||
uint256 const cheriOfferIndex =
|
||||
keylet::nftoffer(cheri, env.seq(cheri)).key;
|
||||
env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
|
||||
token::owner(becky),
|
||||
ter(createOfferTER));
|
||||
env.close();
|
||||
|
||||
// becky buys the nft for 1 drop.
|
||||
uint256 const beckyBuyOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftAutoTrustID, drops(1)),
|
||||
token::owner(alice));
|
||||
env.close();
|
||||
env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
|
||||
env.close();
|
||||
// To keep things tidy, cancel the offers.
|
||||
env(token::cancelOffer(becky, {beckyOfferIndex}));
|
||||
env(token::cancelOffer(cheri, {cheriOfferIndex}));
|
||||
env.close();
|
||||
}
|
||||
// An nft with flagCreateTrustLines but with a non-zero transfer
|
||||
// fee allows transfers using IOUs for payment.
|
||||
{
|
||||
std::uint16_t transferFee = 10000; // 10%
|
||||
|
||||
// becky sells the nft for AUD.
|
||||
uint256 const beckySellOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(cheri, beckySellOfferIndex));
|
||||
env.close();
|
||||
uint256 const nftAutoTrustID{token::getNextID(
|
||||
env, alice, 0u, tfTransferable | tfTrustLine, transferFee)};
|
||||
|
||||
// alice should now have a trust line for gwAUD.
|
||||
BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
|
||||
// If the fixRemoveNFTokenAutoTrustLine amendment is active
|
||||
// then this transaction fails.
|
||||
{
|
||||
TER const mintTER =
|
||||
tweakedFeatures[fixRemoveNFTokenAutoTrustLine]
|
||||
? static_cast<TER>(temINVALID_FLAG)
|
||||
: static_cast<TER>(tesSUCCESS);
|
||||
|
||||
// becky buys the nft back for CAD.
|
||||
uint256 const beckyBuyBackOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftAutoTrustID, gwCAD(50)),
|
||||
token::owner(cheri));
|
||||
env.close();
|
||||
env(token::acceptBuyOffer(cheri, beckyBuyBackOfferIndex));
|
||||
env.close();
|
||||
env(token::mint(alice, 0u),
|
||||
token::xferFee(transferFee),
|
||||
txflags(tfTransferable | tfTrustLine),
|
||||
ter(mintTER));
|
||||
env.close();
|
||||
|
||||
// alice should now have a trust line for gwAUD and gwCAD.
|
||||
BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
|
||||
BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(5));
|
||||
}
|
||||
// Now that alice has trust lines already established, an nft without
|
||||
// flagCreateTrustLines will work for preestablished trust lines.
|
||||
{
|
||||
std::uint16_t transferFee = 5000; // 5%
|
||||
uint256 const nftNoAutoTrustID{
|
||||
token::getNextID(env, alice, 0u, tfTransferable, transferFee)};
|
||||
env(token::mint(alice, 0u),
|
||||
token::xferFee(transferFee),
|
||||
txflags(tfTransferable));
|
||||
env.close();
|
||||
// If fixRemoveNFTokenAutoTrustLine is active the rest
|
||||
// of this test falls on its face.
|
||||
if (tweakedFeatures[fixRemoveNFTokenAutoTrustLine])
|
||||
break;
|
||||
}
|
||||
// becky buys the nft for 1 drop.
|
||||
uint256 const beckyBuyOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftAutoTrustID, drops(1)),
|
||||
token::owner(alice));
|
||||
env.close();
|
||||
env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
|
||||
env.close();
|
||||
|
||||
// alice sells the nft using AUD.
|
||||
uint256 const aliceSellOfferIndex =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftNoAutoTrustID, gwAUD(200)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(cheri, aliceSellOfferIndex));
|
||||
env.close();
|
||||
// becky sells the nft for AUD.
|
||||
uint256 const beckySellOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(cheri, beckySellOfferIndex));
|
||||
env.close();
|
||||
|
||||
// alice should now have AUD(210):
|
||||
// o 200 for this sale and
|
||||
// o 10 for the previous sale's fee.
|
||||
BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(210));
|
||||
// alice should now have a trust line for gwAUD.
|
||||
BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
|
||||
|
||||
// cheri can't sell the NFT for EUR, but can for CAD.
|
||||
env(token::createOffer(cheri, nftNoAutoTrustID, gwEUR(50)),
|
||||
txflags(tfSellNFToken),
|
||||
ter(tecNO_LINE));
|
||||
env.close();
|
||||
uint256 const cheriSellOfferIndex =
|
||||
keylet::nftoffer(cheri, env.seq(cheri)).key;
|
||||
env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(becky, cheriSellOfferIndex));
|
||||
env.close();
|
||||
// becky buys the nft back for CAD.
|
||||
uint256 const beckyBuyBackOfferIndex =
|
||||
keylet::nftoffer(becky, env.seq(becky)).key;
|
||||
env(token::createOffer(becky, nftAutoTrustID, gwCAD(50)),
|
||||
token::owner(cheri));
|
||||
env.close();
|
||||
env(token::acceptBuyOffer(cheri, beckyBuyBackOfferIndex));
|
||||
env.close();
|
||||
|
||||
// alice should now have CAD(10):
|
||||
// o 5 from this sale's fee and
|
||||
// o 5 for the previous sale's fee.
|
||||
BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(10));
|
||||
// alice should now have a trust line for gwAUD and gwCAD.
|
||||
BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
|
||||
BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(5));
|
||||
}
|
||||
// Now that alice has trust lines preestablished, an nft without
|
||||
// flagCreateTrustLines will work for preestablished trust lines.
|
||||
{
|
||||
std::uint16_t transferFee = 5000; // 5%
|
||||
uint256 const nftNoAutoTrustID{token::getNextID(
|
||||
env, alice, 0u, tfTransferable, transferFee)};
|
||||
env(token::mint(alice, 0u),
|
||||
token::xferFee(transferFee),
|
||||
txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
// alice sells the nft using AUD.
|
||||
uint256 const aliceSellOfferIndex =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftNoAutoTrustID, gwAUD(200)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(cheri, aliceSellOfferIndex));
|
||||
env.close();
|
||||
|
||||
// alice should now have AUD(210):
|
||||
// o 200 for this sale and
|
||||
// o 10 for the previous sale's fee.
|
||||
BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(210));
|
||||
|
||||
// cheri can't sell the NFT for EUR, but can for CAD.
|
||||
env(token::createOffer(cheri, nftNoAutoTrustID, gwEUR(50)),
|
||||
txflags(tfSellNFToken),
|
||||
ter(tecNO_LINE));
|
||||
env.close();
|
||||
uint256 const cheriSellOfferIndex =
|
||||
keylet::nftoffer(cheri, env.seq(cheri)).key;
|
||||
env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(becky, cheriSellOfferIndex));
|
||||
env.close();
|
||||
|
||||
// alice should now have CAD(10):
|
||||
// o 5 from this sale's fee and
|
||||
// o 5 for the previous sale's fee.
|
||||
BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user