Compare commits

...

8 Commits

Author SHA1 Message Date
Denis Angell
bae099ee63 rename transactor 2024-06-18 10:34:08 +02:00
Denis Angell
dddc7a6f72 use ic 2024-06-12 11:11:36 +02:00
Denis Angell
f4f3fc0231 Update TxFlags.h 2024-06-10 10:10:52 +02:00
Denis Angell
0c9c93d861 add rpc 2024-05-28 14:30:03 +02:00
Denis Angell
2c5a9bd952 Update Indexes.h 2024-05-28 13:52:31 +02:00
Denis Angell
c77d53b810 update fields 2024-05-28 13:49:40 +02:00
Denis Angell
ba2422929a add rpc 2024-05-28 11:15:21 +02:00
Denis Angell
625ca21d2f options build 2024-05-15 11:33:05 +02:00
50 changed files with 2395 additions and 116 deletions

View File

@@ -3,7 +3,7 @@
"C_Cpp.clang_format_path": ".clang-format",
"C_Cpp.clang_format_fallbackStyle": "{ ColumnLimit: 0 }",
"[cpp]":{
"editor.wordBasedSuggestions": false,
"editor.wordBasedSuggestions": "off",
"editor.suggest.insertMode": "replace",
"editor.semanticHighlighting.enabled": true,
"editor.tabSize": 4,

View File

@@ -88,6 +88,7 @@ target_sources (xrpl_core PRIVATE
src/ripple/protocol/impl/Issue.cpp
src/ripple/protocol/impl/Keylet.cpp
src/ripple/protocol/impl/LedgerFormats.cpp
src/ripple/protocol/impl/Option.cpp
src/ripple/protocol/impl/PublicKey.cpp
src/ripple/protocol/impl/Quality.cpp
src/ripple/protocol/impl/Rate2.cpp
@@ -219,6 +220,7 @@ install (
src/ripple/protocol/Keylet.h
src/ripple/protocol/KnownFormats.h
src/ripple/protocol/LedgerFormats.h
src/ripple/protocol/Option.h
src/ripple/protocol/Protocol.h
src/ripple/protocol/PublicKey.h
src/ripple/protocol/Quality.h
@@ -446,6 +448,9 @@ target_sources (rippled PRIVATE
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/OptionCreate.cpp
src/ripple/app/tx/impl/OptionExercise.cpp
src/ripple/app/tx/impl/OptionList.cpp
src/ripple/app/tx/impl/PayChan.cpp
src/ripple/app/tx/impl/Payment.cpp
src/ripple/app/tx/impl/SetAccount.cpp
@@ -629,6 +634,7 @@ target_sources (rippled PRIVATE
src/ripple/rpc/handlers/NFTOffers.cpp
src/ripple/rpc/handlers/NodeToShard.cpp
src/ripple/rpc/handlers/NoRippleCheck.cpp
src/ripple/rpc/handlers/OptionBookOffers.cpp
src/ripple/rpc/handlers/OwnerInfo.cpp
src/ripple/rpc/handlers/PathFind.cpp
src/ripple/rpc/handlers/PayChanClaim.cpp
@@ -732,6 +738,7 @@ if (tests)
src/test/app/NFTokenBurn_test.cpp
src/test/app/NFTokenDir_test.cpp
src/test/app/OfferStream_test.cpp
src/test/app/Option_test.cpp
src/test/app/Offer_test.cpp
src/test/app/OversizeMeta_test.cpp
src/test/app/Path_test.cpp

View File

@@ -1,12 +1,17 @@
#include <ripple/app/hook/applyHook.h>
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/app/ledger/TransactionMaster.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/tx/impl/Import.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/Slice.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
#include <ripple/protocol/tokens.h>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <any>

View File

@@ -85,9 +85,11 @@ OrderBookDB::update(std::shared_ptr<ReadView const> const& ledger)
return;
}
decltype(optionBooks_) optionBooks;
decltype(allBooks_) allBooks;
decltype(xrpBooks_) xrpBooks;
optionBooks.reserve(optionBooks_.size());
allBooks.reserve(allBooks_.size());
xrpBooks.reserve(xrpBooks_.size());
@@ -161,6 +163,14 @@ OrderBookDB::addOrderBook(Book const& book)
xrpBooks_.insert(book.in);
}
void
OrderBookDB::addOptionOrderBook(Option const& option)
{
std::lock_guard sl(mLock);
optionBooks_.insert(option.issue);
}
// return list of all orderbooks that want this issuerID and currencyID
std::vector<Book>
OrderBookDB::getBooksByTakerPays(Issue const& issue)

View File

@@ -23,6 +23,7 @@
#include <ripple/app/ledger/AcceptedLedgerTx.h>
#include <ripple/app/ledger/BookListeners.h>
#include <ripple/app/main/Application.h>
#include <ripple/protocol/Option.h>
#include <mutex>
namespace ripple {
@@ -40,6 +41,9 @@ public:
void
addOrderBook(Book const&);
void
addOptionOrderBook(Option const&);
/** @return a list of all orderbooks that want this issuerID and currencyID.
*/
std::vector<Book>
@@ -55,6 +59,7 @@ public:
BookListeners::pointer
getBookListeners(Book const&);
BookListeners::pointer
makeBookListeners(Book const&);
@@ -71,6 +76,9 @@ private:
// Maps order books by "issue in" to "issue out":
hardened_hash_map<Issue, hardened_hash_set<Issue>> allBooks_;
// does an order book to XRP exist
hash_set<Issue> optionBooks_;
// does an order book to XRP exist
hash_set<Issue> xrpBooks_;

View File

@@ -350,6 +350,15 @@ public:
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult) override;
void
getOptionBookPage(
std::shared_ptr<ReadView const>& lpLedger,
STAmount const&,
std::uint32_t const& expiration,
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult) override;
// Ledger proposal/close functions.
bool
@@ -4581,6 +4590,114 @@ NetworkOPsImp::getBookPage(
#endif
void
NetworkOPsImp::getOptionBookPage(
std::shared_ptr<ReadView const>& lpLedger,
STAmount const& strikePrice,
std::uint32_t const& expiration,
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult)
{
Json::Value& jvOffers = (jvResult[jss::offers] = Json::Value(Json::arrayValue));
const uint256 uBookBase = getOptionBookBase(
strikePrice.getIssuer(),
strikePrice.getCurrency(),
strikePrice.mantissa(),
expiration);
const uint256 uBookEnd = getOptionQualityNext(uBookBase);
uint256 uTipIndex = uBookBase;
if (auto stream = m_journal.trace())
{
stream << "getBookPage:" << strikePrice;
stream << "getBookPage: uBookBase=" << uBookBase;
stream << "getBookPage: uBookEnd=" << uBookEnd;
stream << "getBookPage: uTipIndex=" << uTipIndex;
}
ReadView const& view = *lpLedger;
bool bDone = false;
bool bDirectAdvance = true;
std::shared_ptr<SLE const> sleOfferDir;
uint256 offerIndex;
unsigned int uBookEntry;
STAmount saDirRate;
auto viewJ = app_.journal("View");
while (!bDone && iLimit-- > 0)
{
if (bDirectAdvance)
{
bDirectAdvance = false;
JLOG(m_journal.trace()) << "getBookPage: bDirectAdvance";
auto const ledgerIndex = view.succ(uTipIndex, uBookEnd);
if (ledgerIndex)
{
sleOfferDir = view.read(keylet::page(*ledgerIndex));
}
else
{
sleOfferDir.reset();
}
if (!sleOfferDir)
{
JLOG(m_journal.trace()) << "getBookPage: bDone";
bDone = true;
}
else
{
uTipIndex = sleOfferDir->key();
cdirFirst(view, uTipIndex, sleOfferDir, uBookEntry, offerIndex);
JLOG(m_journal.trace())
<< "getBookPage: uTipIndex=" << uTipIndex;
JLOG(m_journal.trace())
<< "getBookPage: offerIndex=" << offerIndex;
}
}
if (!bDone)
{
auto sleOffer = view.read(keylet::optionOffer(offerIndex));
if (sleOffer)
{
STAmount premium = sleOffer->getFieldAmount(sfAmount);
auto const optionQuality = getOptionQuality(uTipIndex);
STAmount saDirRate = STAmount(premium.issue(), optionQuality);
Json::Value jvOffer = sleOffer->getJson(JsonOptions::none);
Json::Value& jvOf = jvOffers.append(jvOffer);
jvOf[jss::quality] = saDirRate.getText();
}
else
{
JLOG(m_journal.warn()) << "Missing offer";
}
if (!cdirNext(view, uTipIndex, sleOfferDir, uBookEntry, offerIndex))
{
bDirectAdvance = true;
}
else
{
JLOG(m_journal.trace())
<< "getBookPage: offerIndex=" << offerIndex;
}
}
}
}
inline void
NetworkOPsImp::collect_metrics()
{

View File

@@ -163,6 +163,15 @@ public:
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult) = 0;
virtual void
getOptionBookPage(
std::shared_ptr<ReadView const>& lpLedger,
STAmount const& strikePrice,
std::uint32_t const& expiration,
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult) = 0;
//--------------------------------------------------------------------------

View File

@@ -109,6 +109,10 @@ XRPNotCreated::visitEntry(
if (isXRP((*before)[sfAmount]))
drops_ -= (*before)[sfAmount].xrp().drops();
break;
case ltOPTION_OFFER:
if (isXRP((*before)[sfLockedBalance]))
drops_ -= (*before)[sfLockedBalance].xrp().drops();
break;
default:
break;
}
@@ -131,6 +135,10 @@ XRPNotCreated::visitEntry(
if (!isDelete && isXRP((*after)[sfAmount]))
drops_ += (*after)[sfAmount].xrp().drops();
break;
case ltOPTION_OFFER:
if (!isDelete && isXRP((*after)[sfLockedBalance]))
drops_ += (*after)[sfLockedBalance].xrp().drops();
break;
default:
break;
}
@@ -492,6 +500,8 @@ LedgerEntryTypesMatch::visitEntry(
case ltURI_TOKEN:
case ltIMPORT_VLSEQ:
case ltUNL_REPORT:
case ltOPTION:
case ltOPTION_OFFER:
break;
default:
invalidTypeAdded_ = true;

View File

@@ -0,0 +1,473 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/ledger/OrderBookDB.h>
#include <ripple/app/tx/impl/OptionCreate.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/ApplyView.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Option.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
TxConsequences
OptionCreate::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
OptionCreate::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (ctx.tx.getFlags() & tfOptionCreateMask)
{
JLOG(ctx.j.warn()) << "OptionCreate: Invalid flags set.";
return temINVALID_FLAG;
}
auto const flags = ctx.tx.getFlags();
std::optional<uint256> const swapID = ctx.tx[~sfSwapID];
std::uint32_t const quantity = ctx.tx.getFieldU32(sfQuantity);
bool const isClose = (flags & tfPosition) != 0;
if (isClose && !swapID)
{
JLOG(ctx.j.warn())
<< "OptionCreate: Cannot close option without swap ID.";
return temMALFORMED;
}
if (quantity % 100)
{
JLOG(ctx.j.warn())
<< "OptionCreate: Quantity must be a multple of 100.";
return temMALFORMED;
}
}
std::optional<uint256>
sealOption(
Sandbox& sb,
STAmount strikePrice,
std::uint32_t expiration,
bool isPut,
bool isSell)
{
const uint256 uBookBaseOpp = getOptionBookBase(
strikePrice.getIssuer(),
strikePrice.getCurrency(),
strikePrice.mantissa(),
expiration);
// std::cout << "BOOK BASE: " << uBookBaseOpp << "\n";
const uint256 uBookEndOpp = getOptionQualityNext(uBookBaseOpp);
// std::cout << "BOOK BASE END: " << uBookEndOpp << "\n";
auto key = sb.succ(uBookBaseOpp, uBookEndOpp);
if (!key)
{
return std::nullopt;
}
auto sleOfferDir = sb.read(keylet::page(key.value()));
uint256 offerIndex;
unsigned int bookEntry;
if (!cdirFirst(sb, sleOfferDir->key(), sleOfferDir, bookEntry, offerIndex))
{
return std::nullopt;
}
// auto sleOffer = sb.read(keylet::unchecked(offerIndex));
// STAmount premium = sleOffer->getFieldAmount(sfAmount);
// auto const dir = to_string(sleOffer->getFieldH256(sfBookDirectory));
// std::cout << "dir: " << dir << "\n";
// auto const uTipIndex = sleOfferDir->key();
// auto const optionQuality = getOptionQuality(uTipIndex);
// std::cout << "optionQuality: " << optionQuality << "\n";
// STAmount dirRate = STAmount(premium.issue(), optionQuality);
// std::cout << "dirRate: " << dirRate << "\n";
// BEAST_EXPECT(100 / rate == 110);
do
{
auto sleItem = sb.read(keylet::child(offerIndex));
if (!sleItem)
{
continue;
}
auto const flags = sleItem->getFlags();
bool const _isPut = (flags & tfType) != 0;
bool const _isSell = (flags & tfAction) != 0;
// Skip Sealed Options
if (sleItem->getFieldU32(sfOpenInterest) == 0)
{
continue;
}
// looking for oposite option position.
if (_isPut == isPut && _isSell != isSell)
{
uint256 const sealID = sleItem->getFieldH256(sfSwapID);
if (sealID.isNonZero())
{
continue;
}
else
{
return offerIndex;
}
}
} while (
cdirNext(sb, sleOfferDir->key(), sleOfferDir, bookEntry, offerIndex));
return std::nullopt;
}
TER
OptionCreate::doApply()
{
if (!view().rules().enabled(featureOptions))
return temDISABLED;
Sandbox sb(&ctx_.view());
beast::Journal const& j = ctx_.journal;
AccountID const srcAccID = ctx_.tx.getAccountID(sfAccount);
uint256 const optionID = ctx_.tx.getFieldH256(sfOptionID);
auto const flags = ctx_.tx.getFlags();
STAmount const premium = ctx_.tx.getFieldAmount(sfAmount);
std::uint32_t const quantity = ctx_.tx.getFieldU32(sfQuantity);
std::optional<uint256> const swapID = ctx_.tx[~sfSwapID];
STAmount const totalPremium = mulRound(premium, STAmount(premium.issue(), quantity), premium.issue(), false);
auto sleSrcAcc = sb.peek(keylet::account(srcAccID));
if (!sleSrcAcc)
return terNO_ACCOUNT;
auto sleOptionAcc = sb.peek(keylet::unchecked(optionID));
if (!sleOptionAcc)
return tecINTERNAL;
STAmount const strikePrice = sleOptionAcc->getFieldAmount(sfStrikePrice);
std::uint32_t const expiration = sleOptionAcc->getFieldU32(sfExpiration);
AccountID const issuer = sleOptionAcc->getAccountID(sfIssuer);
Currency const currency = Currency(sleOptionAcc->getFieldH160(sfCurrency));
// STAmount const quantityShares = STAmount({ issuer, currency }, isXRP(strikePrice) ? quantity * 1000000 : quantity);
STAmount const quantityShares = STAmount(Issue(currency, issuer), quantity);
if (strikePrice.issue() != totalPremium.issue() || strikePrice.issue() != totalPremium.issue())
return temBAD_ISSUER;
bool const isPut = (flags & tfType) != 0;
bool const isSell = (flags & tfAction) != 0;
bool const isClose = (flags & tfPosition) != 0;
std::cout << "OptionCreate.getIssuer(): " << strikePrice.getIssuer() << "\n";
std::cout << "OptionCreate.getCurrency(): " << strikePrice.getCurrency() << "\n";
std::cout << "OptionCreate.mantissa(): " << strikePrice.mantissa() << "\n";
std::cout << "OptionCreate.exponent(): " << strikePrice.exponent() << "\n";
std::cout << "OptionCreate.value(): " << strikePrice.value() << "\n";
std::cout << "OptionCreate.expiration: " << expiration << "\n";
auto optionBookDirKeylet = keylet::optionBook(
strikePrice.getIssuer(),
strikePrice.getCurrency(),
strikePrice.mantissa(),
expiration);
auto optionOfferKeylet =
ripple::keylet::optionOffer(srcAccID, ctx_.tx.getSeqProxy().value());
std::optional<uint256> const sealID =
sealOption(sb, strikePrice, expiration, isPut, isSell);
// Update Sealed Option
AccountID oppAccID{AccountID{}};
if (sealID)
{
JLOG(j.warn()) << "Updating Sealed Offer: sealID";
auto sealedKeylet = ripple::keylet::unchecked(*sealID);
auto sealedOption = sb.peek(sealedKeylet);
sealedOption->setFieldH256(sfSwapID, optionOfferKeylet.key);
uint32_t currSealed = sealedOption->getFieldU32(sfOpenInterest);
sealedOption->setFieldU32(sfOpenInterest, currSealed - quantity);
oppAccID = sealedOption->getAccountID(sfOwner);
sb.update(sealedOption);
}
// Block No Market Buys
if (!isSell && !sealID)
{
JLOG(j.warn()) << "OptionCreate: No open sell offers exist.";
return tecNO_TARGET;
}
// Insert New Option (3 Times [issuer, party, counter-party])
if (!isClose)
{
JLOG(j.warn()) << "Creating Option Offer: !isClose";
// Add Option to Issuer
// if (!isXRP(premium))
// {
// // unimplemented
// return tecINTERNAL;
// }
// Add Option to Self
std::uint32_t ownerCount{(*sleSrcAcc)[sfOwnerCount]};
++ownerCount;
XRPAmount const newReserve{sb.fees().accountReserve(ownerCount)};
adjustOwnerCount(sb, sleSrcAcc, 1, j);
auto optionOffer = std::make_shared<SLE>(optionOfferKeylet);
auto const page = sb.dirInsert(
keylet::ownerDir(account_),
optionOfferKeylet,
describeOwnerDir(account_));
if (!page)
{
std::cout << "NO PAGE"
<< "\n";
return tecDIR_FULL;
}
optionOffer->setFlag(flags);
optionOffer->setAccountID(sfOwner, srcAccID);
optionOffer->setFieldU64(sfOwnerNode, *page);
optionOffer->setFieldH256(sfOptionID, optionID);
optionOffer->setFieldU32(sfQuantity, quantity);
optionOffer->setFieldU32(sfOpenInterest, quantity);
optionOffer->setFieldAmount(sfAmount, premium); // Premium
optionOffer->setFieldAmount(sfLockedBalance, STAmount(0)); // Locked
if (sealID)
{
JLOG(j.warn()) << "Updating Option Offer: sealID";
optionOffer->setFieldH256(sfSwapID, *sealID);
optionOffer->setFieldU32(sfOpenInterest, 0);
}
if (isSell)
{
JLOG(j.warn()) << "Updating Option Offer: isSell";
// Update the locked balance
optionOffer->setFieldAmount(sfLockedBalance, quantityShares); // Locked
}
Option const option{
strikePrice.issue(), strikePrice.mantissa(), expiration};
// std::cout << "BOOK BASE: " << strikePrice.mantissa() << "\n";
// std::cout << "PREMIUM: " << premium.mantissa() << "\n";
auto dir =
keylet::optionQuality(optionBookDirKeylet, premium.mantissa());
bool const bookExisted = static_cast<bool>(sb.peek(dir));
auto const bookNode =
sb.dirAppend(dir, optionOfferKeylet, [&](SLE::ref sle) {
sle->setFieldH160(
sfTakerPaysIssuer, strikePrice.issue().account);
sle->setFieldH160(
sfTakerPaysCurrency, strikePrice.issue().currency);
sle->setFieldU64(sfBaseFee, strikePrice.mantissa());
sle->setFieldU32(sfExpiration, expiration);
sle->setFieldU64(sfExchangeRate, premium.mantissa());
});
if (!bookNode)
{
JLOG(j_.debug()) << "final result: failed to add offer to book";
return tecDIR_FULL;
}
// if (!bookExisted)
// ctx_.app.getOrderBookDB().addOptionOrderBook(option);
optionOffer->setFieldH256(sfBookDirectory, dir.key);
optionOffer->setFieldU64(sfBookNode, *bookNode);
sb.insert(optionOffer);
}
if (isClose)
{
JLOG(j.warn()) << "Updating Option Offer: isClose";
auto optionOffer = sb.peek(optionOfferKeylet);
uint256 const sealID = optionOffer->getFieldH256(sfSwapID);
if (!sealID)
{
JLOG(j.warn())
<< "OptionCreate: Cannot close option that has not sealed.";
return tecINTERNAL;
}
// Update Swap Option
auto swapKeylet = ripple::keylet::unchecked(*swapID);
auto swapOption = sb.peek(swapKeylet);
if (!swapOption)
{
JLOG(j.warn()) << "OptionCreate: Swap Option does not exist.";
return tecINTERNAL;
}
swapOption->setFieldH256(sfSwapID, optionOfferKeylet.key);
sb.update(swapOption);
// Update New Option
optionOffer->setFieldH256(sfSwapID, *swapID);
sb.update(optionOffer);
// Erase Swap Sealed Option
uint256 const swapSealedID = swapOption->getFieldH256(sfSwapID);
if (!swapSealedID)
{
JLOG(j.warn()) << "OptionCreate: Swap Option is not sealed.";
return tecINTERNAL;
}
auto swapSealedKeylet = ripple::keylet::unchecked(swapSealedID);
auto swapSealedOption = sb.peek(swapSealedKeylet);
if (!swapSealedOption)
{
JLOG(j.warn())
<< "OptionCreate: Swap Sealed Option does not exist.";
return tecINTERNAL;
}
sb.erase(swapSealedOption);
// Erase New Sealed Option
auto sealedKeylet = ripple::keylet::unchecked(sealID);
auto sealedOption = sb.peek(sealedKeylet);
sb.erase(sealedOption);
}
// xfer the premium from the buyer to the seller
if (!isSell && sealID)
{
JLOG(j.warn()) << "Updating Option Balances: !isSell && sealID";
auto sleOppAcc = sb.peek(keylet::account(oppAccID));
if (!sleOppAcc)
return terNO_ACCOUNT;
// Native
if (isXRP(premium))
{
// ensure the account can cover the native
if (mSourceBalance < totalPremium.xrp())
return tecUNFUNDED_PAYMENT;
// subtract the premium from the buyer
{
STAmount bal = mSourceBalance;
bal -= totalPremium.xrp();
if (bal < beast::zero || bal > mSourceBalance)
return tecINTERNAL;
sleSrcAcc->setFieldAmount(sfBalance, bal);
}
// add the premium to the writer
{
STAmount bal = sleOppAcc->getFieldAmount(sfBalance);
STAmount prior = bal;
bal += totalPremium.xrp();
if (bal < beast::zero || bal < prior)
return tecINTERNAL;
sleOppAcc->setFieldAmount(sfBalance, bal);
}
}
else
{
// // 1. & 2.
TER canBuyerXfer = trustTransferAllowed(
sb,
std::vector<AccountID>{srcAccID, oppAccID},
totalPremium.issue(),
j);
if (!isTesSuccess(canBuyerXfer))
{
return canBuyerXfer;
}
STAmount availableBuyerFunds{accountFunds(
sb, srcAccID, totalPremium, fhZERO_IF_FROZEN, j)};
JLOG(j.warn()) << "availableBuyerFunds: " << availableBuyerFunds << "/n";
JLOG(j.warn()) << "totalPremium: " << totalPremium << "/n";
if (availableBuyerFunds < totalPremium)
return tecUNFUNDED_PAYMENT;
if (TER result = accountSend(sb, srcAccID, oppAccID, totalPremium, j, true); !isTesSuccess(result))
return result;
}
sb.update(sleOppAcc);
}
else
{
JLOG(j.warn()) << "Updating Option Balances: isSell";
if (isXRP(quantityShares))
{
// subtract the quantity from the writer
if (mSourceBalance < quantityShares.xrp())
return tecUNFUNDED_PAYMENT;
{
STAmount bal = mSourceBalance;
bal -= quantityShares.xrp();
if (bal < beast::zero || bal > mSourceBalance)
return tecINTERNAL;
sleSrcAcc->setFieldAmount(sfBalance, bal);
}
}
else
{
STAmount availableBuyerFunds{accountFunds(
sb, srcAccID, quantityShares, fhZERO_IF_FROZEN, j)};
if (availableBuyerFunds < quantityShares)
return tecUNFUNDED_PAYMENT;
std::shared_ptr<SLE> sleLine = ctx_.view().peek(keylet::line(srcAccID, quantityShares.getIssuer(), quantityShares.getCurrency()));
if (TER const result = trustAdjustLockedBalance(ctx_.view(), sleLine, quantityShares, 1, ctx_.journal, WetRun); !isTesSuccess(result))
return result;
}
}
// apply
sb.update(sleSrcAcc);
sb.apply(ctx_.rawView());
return tesSUCCESS;
}
XRPAmount
OptionCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return Transactor::calculateBaseFee(view, tx);
}
} // namespace ripple

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 XRPL-Labs
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_OPTIONCREATE_H_INCLUDED
#define RIPPLE_TX_OPTIONCREATE_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
class OptionCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit OptionCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
};
} // namespace ripple
#endif

View File

@@ -0,0 +1,258 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 XRPL-Labs
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/OptionExercise.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/ApplyView.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
TxConsequences
OptionExercise::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
OptionExercise::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (ctx.tx.getFlags() & tfOptionExpireMask)
{
// There are no flags (other than universal).
JLOG(ctx.j.warn()) << "Malformed transaction: Invalid flags set.";
return temINVALID_FLAG;
}
}
TER
OptionExercise::doApply()
{
if (!view().rules().enabled(featureOptions))
return temDISABLED;
// Sandbox sb(&ctx_.view());
beast::Journal const& j = ctx_.journal;
AccountID const srcAccID = ctx_.tx.getAccountID(sfAccount);
uint256 const optionID = ctx_.tx.getFieldH256(sfOptionID);
uint256 const offerID = ctx_.tx.getFieldH256(sfSwapID);
auto const flags = ctx_.tx.getFlags();
auto sleSrcAcc = ctx_.view().peek(keylet::account(srcAccID));
if (!sleSrcAcc)
return terNO_ACCOUNT;
auto optionOfferKeylet = ripple::keylet::unchecked(offerID);
auto sleOptionOffer = ctx_.view().peek(optionOfferKeylet);
if (!sleOptionOffer)
return tecNO_TARGET;
AccountID const ownrAccID = sleOptionOffer->getAccountID(sfOwner);
auto const sealedID = sleOptionOffer->getFieldH256(sfSwapID);
auto oppOfferKeylet = ripple::keylet::unchecked(sealedID);
auto sleSealedOffer = ctx_.view().peek(oppOfferKeylet);
if (!sleSealedOffer)
return tecNO_TARGET;
AccountID const oppAccID = sleSealedOffer->getAccountID(sfOwner);
auto sleOppAcc = ctx_.view().peek(keylet::account(oppAccID));
if (!sleOppAcc)
return terNO_ACCOUNT;
auto const optionFlags = sleOptionOffer->getFlags();
bool const isPut = (optionFlags & tfType) != 0;
bool const isSell = (optionFlags & tfAction) != 0;
auto sleOption = ctx_.view().peek(keylet::unchecked(optionID));
if (!sleOption)
return tecINTERNAL;
STAmount const strikePrice = sleOption->getFieldAmount(sfStrikePrice);
STAmount const quantityShares = sleSealedOffer->getFieldAmount(sfLockedBalance);
std::uint32_t const quantity = sleOptionOffer->getFieldU32(sfQuantity);
STAmount const totalValue = mulRound(strikePrice, STAmount(strikePrice.issue(), quantity), strikePrice.issue(), false);
JLOG(j.warn()) << "OptionExercise: QUANTITY SHARES" << quantityShares << "\n";
JLOG(j.warn()) << "OptionExercise: TOTAL VALUE" << totalValue << "\n";
if (flags & tfOptionExpire)
{
JLOG(j.warn()) << "OptionExercise: EXPIRE OPTION";
ctx_.view().erase(sleSealedOffer);
ctx_.view().erase(sleOptionOffer);
// sb.apply(ctx_.rawView());
return tesSUCCESS;
}
switch (isPut)
{
case 0: {
JLOG(j.warn()) << "OptionExercise: EXERCISE CALL";
STAmount hBalance = mSourceBalance;
// subtract the total value from the buyer
// add the total value to the writer
if (isXRP(totalValue))
{
if (mSourceBalance < totalValue.xrp())
return tecUNFUNDED_PAYMENT;
// 1.
hBalance -= totalValue.xrp();
if (hBalance < beast::zero || hBalance > mSourceBalance)
return tecINTERNAL;
// 2.
STAmount wBalance = sleOppAcc->getFieldAmount(sfBalance);
STAmount prior = wBalance;
wBalance += totalValue.xrp();
if (wBalance < beast::zero || wBalance < prior)
return tecINTERNAL;
sleOppAcc->setFieldAmount(sfBalance, wBalance);
}
else
{
// 1. & 2.
TER canBuyerXfer = trustTransferAllowed(
ctx_.view(),
std::vector<AccountID>{srcAccID, oppAccID},
totalValue.issue(),
j);
if (!isTesSuccess(canBuyerXfer))
{
return canBuyerXfer;
}
STAmount availableBuyerFunds{accountFunds(
ctx_.view(), srcAccID, totalValue, fhZERO_IF_FROZEN, j)};
if (availableBuyerFunds < totalValue)
return tecUNFUNDED_PAYMENT;
if (TER result = accountSend(
ctx_.view(), srcAccID, oppAccID, totalValue, j, true);
!isTesSuccess(result))
return result;
}
// add the shares to the buyer
if (isXRP(quantityShares))
{
STAmount prior = hBalance;
hBalance += quantityShares.xrp();
if (hBalance < beast::zero || hBalance < prior)
return tecINTERNAL;
sleSrcAcc->setFieldAmount(sfBalance, hBalance);
}
else
{
// Rate lockedRate = ripple::Rate(slep->getFieldU32(sfTransferRate));
// auto const issuerAccID = totalPremium.getIssuer();
// auto const xferRate = transferRate(view(), issuerAccID);
// // // update if issuer rate is less than locked rate
// // if (xferRate < lockedRate)
// // lockedRate = xferRate;
// all the significant complexity of checking the validity of this
// transfer and ensuring the lines exist etc is hidden away in this
// function, all we need to do is call it and return if unsuccessful.
TER const result = trustTransferLockedBalance(
ctx_.view(),
account_, // txn signing account
sleOppAcc, // src account
sleSrcAcc, // dst account
quantityShares, // xfer amount
-1,
parityRate,
j_,
WetRun // wet run;
);
if (!isTesSuccess(result))
return result;
}
break;
}
case 1: {
JLOG(j.warn()) << "OptionExercise: EXERCISE PUT";
if (isXRP(quantityShares))
{
// add the total value to the holder
{
STAmount hBalance = mSourceBalance;
hBalance += totalValue.xrp();
if (hBalance < beast::zero || hBalance < mSourceBalance)
return tecINTERNAL;
sleSrcAcc->setFieldAmount(sfBalance, hBalance);
}
STAmount wBalance = sleOppAcc->getFieldAmount(sfBalance);
if (wBalance < totalValue.xrp())
return tecUNFUNDED_PAYMENT;
// subtract the total value from the writer
{
STAmount prior = wBalance;
wBalance -= totalValue.xrp();
if (wBalance < beast::zero || wBalance > prior)
return tecINTERNAL;
}
// add the shares to the writer
{
STAmount prior = wBalance;
wBalance += quantityShares.xrp();
if (wBalance < beast::zero || wBalance < prior)
return tecINTERNAL;
}
sleOppAcc->setFieldAmount(sfBalance, wBalance);
}
break;
}
default:
return tecINTERNAL;
}
// apply
ctx_.view().update(sleOppAcc);
ctx_.view().update(sleSrcAcc);
ctx_.view().erase(sleSealedOffer);
ctx_.view().erase(sleOptionOffer);
// sb.apply(ctx_.rawView());
return tesSUCCESS;
}
XRPAmount
OptionExercise::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return Transactor::calculateBaseFee(view, tx);
}
} // namespace ripple

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 XRPL-Labs
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_OPTIONEXECUTE_H_INCLUDED
#define RIPPLE_TX_OPTIONEXECUTE_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
class OptionExercise : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit OptionExercise(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
};
} // namespace ripple
#endif

View File

@@ -0,0 +1,109 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 XRPL-Labs
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/OptionList.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/ApplyView.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
TxConsequences
OptionList::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
OptionList::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
JLOG(ctx.j.warn()) << (ctx.tx.getFlags()) << "\n";
JLOG(ctx.j.warn()) << (ctx.tx.getFlags() & tfUniversalMask) << "\n";
if (ctx.tx.getFlags() & tfUniversalMask)
{
// There are no flags (other than universal).
JLOG(ctx.j.warn()) << "Malformed transaction: Invalid flags set.";
return temINVALID_FLAG;
}
}
TER
OptionList::doApply()
{
if (!view().rules().enabled(featureOptions))
return temDISABLED;
Sandbox sb(&ctx_.view());
beast::Journal const& j = ctx_.journal;
AccountID const srcAccID = ctx_.tx.getAccountID(sfAccount);
std::uint32_t const expiration = ctx_.tx.getFieldU32(sfExpiration);
STAmount const strikePrice = ctx_.tx.getFieldAmount(sfStrikePrice);
AccountID const issuer = ctx_.tx.getAccountID(sfIssuer);
auto const currency = ctx_.tx.getFieldH160(sfCurrency);
auto sleSrcAcc = sb.peek(keylet::account(srcAccID));
if (!sleSrcAcc)
return terNO_ACCOUNT;
std::optional<Keylet> const optionKeylet = keylet::option(strikePrice.getIssuer(), strikePrice.getCurrency(), strikePrice.mantissa(), expiration);
if (sb.exists(*optionKeylet))
return tecDUPLICATE;
auto const sleOption = std::make_shared<SLE>(*optionKeylet);
auto const newPage = sb.dirInsert(
keylet::ownerDir(strikePrice.getIssuer()),
*optionKeylet,
describeOwnerDir(strikePrice.getIssuer()));
JLOG(j_.trace()) << "Adding Option to owner directory "
<< to_string(optionKeylet->key) << ": "
<< (newPage ? "success" : "failure");
if (!newPage)
return tecDIR_FULL;
(*sleOption)[sfOwnerNode] = *newPage;
(*sleOption)[sfStrikePrice] = strikePrice;
(*sleOption)[sfIssuer] = issuer;
(*sleOption)[sfCurrency] = currency;
(*sleOption)[sfExpiration] = expiration;
// apply
sb.insert(sleOption);
sb.apply(ctx_.rawView());
return tesSUCCESS;
}
XRPAmount
OptionList::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return Transactor::calculateBaseFee(view, tx);
}
} // namespace ripple

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 XRPL-Labs
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_OPTIONLIST_H_INCLUDED
#define RIPPLE_TX_OPTIONLIST_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
class OptionList : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit OptionList(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
};
} // namespace ripple
#endif

View File

@@ -23,6 +23,7 @@
#include <ripple/app/tx/impl/CancelOffer.h>
#include <ripple/app/tx/impl/CashCheck.h>
#include <ripple/app/tx/impl/Change.h>
#include <ripple/app/tx/impl/ClaimReward.h>
#include <ripple/app/tx/impl/CreateCheck.h>
#include <ripple/app/tx/impl/CreateOffer.h>
#include <ripple/app/tx/impl/CreateTicket.h>
@@ -37,8 +38,12 @@
#include <ripple/app/tx/impl/NFTokenCancelOffer.h>
#include <ripple/app/tx/impl/NFTokenCreateOffer.h>
#include <ripple/app/tx/impl/NFTokenMint.h>
#include <ripple/app/tx/impl/OptionCreate.h>
#include <ripple/app/tx/impl/OptionExercise.h>
#include <ripple/app/tx/impl/OptionList.h>
#include <ripple/app/tx/impl/PayChan.h>
#include <ripple/app/tx/impl/Payment.h>
#include <ripple/app/tx/impl/Remit.h>
#include <ripple/app/tx/impl/SetAccount.h>
#include <ripple/app/tx/impl/SetHook.h>
#include <ripple/app/tx/impl/SetRegularKey.h>
@@ -122,6 +127,12 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<EscrowFinish>(ctx);
case ttESCROW_CANCEL:
return invoke_preflight_helper<EscrowCancel>(ctx);
case ttOPTION_CREATE:
return invoke_preflight_helper<OptionCreate>(ctx);
case ttOPTION_EXERCISE:
return invoke_preflight_helper<OptionExercise>(ctx);
case ttOPTION_LIST:
return invoke_preflight_helper<OptionList>(ctx);
case ttPAYCHAN_CLAIM:
return invoke_preflight_helper<PayChanClaim>(ctx);
case ttPAYCHAN_CREATE:
@@ -243,6 +254,12 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<EscrowFinish>(ctx);
case ttESCROW_CANCEL:
return invoke_preclaim<EscrowCancel>(ctx);
case ttOPTION_CREATE:
return invoke_preclaim<OptionCreate>(ctx);
case ttOPTION_EXERCISE:
return invoke_preclaim<OptionExercise>(ctx);
case ttOPTION_LIST:
return invoke_preclaim<OptionList>(ctx);
case ttPAYCHAN_CLAIM:
return invoke_preclaim<PayChanClaim>(ctx);
case ttPAYCHAN_CREATE:
@@ -326,6 +343,12 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return EscrowFinish::calculateBaseFee(view, tx);
case ttESCROW_CANCEL:
return EscrowCancel::calculateBaseFee(view, tx);
case ttOPTION_CREATE:
return OptionCreate::calculateBaseFee(view, tx);
case ttOPTION_EXERCISE:
return OptionExercise::calculateBaseFee(view, tx);
case ttOPTION_LIST:
return OptionList::calculateBaseFee(view, tx);
case ttPAYCHAN_CLAIM:
return PayChanClaim::calculateBaseFee(view, tx);
case ttPAYCHAN_CREATE:
@@ -469,6 +492,18 @@ invoke_apply(ApplyContext& ctx)
EscrowCancel p(ctx);
return p();
}
case ttOPTION_CREATE: {
OptionCreate p(ctx);
return p();
}
case ttOPTION_EXERCISE: {
OptionExercise p(ctx);
return p();
}
case ttOPTION_LIST: {
OptionList p(ctx);
return p();
}
case ttPAYCHAN_CLAIM: {
PayChanClaim p(ctx);
return p();

View File

@@ -30,7 +30,7 @@ mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
{
using namespace boost::multiprecision;
uint128_t result;
boost::multiprecision::uint128_t result;
result = multiply(result, value, mul);
result /= div;

View File

@@ -145,111 +145,78 @@ private:
};
// VFALCO TODO This should only be enabled for maps.
class pair_value_compare
: public beast::detail::empty_base_optimization<Compare>
#ifdef _LIBCPP_VERSION
,
public std::binary_function<value_type, value_type, bool>
#endif
class pair_value_compare : public Compare
{
public:
#ifndef _LIBCPP_VERSION
using first_argument = value_type;
using second_argument = value_type;
using result_type = bool;
#endif
bool
operator()(value_type const& lhs, value_type const& rhs) const
{
return this->member()(lhs.first, rhs.first);
return Compare::operator()(lhs.first, rhs.first);
}
pair_value_compare()
{
}
pair_value_compare(pair_value_compare const& other)
: beast::detail::empty_base_optimization<Compare>(other)
pair_value_compare(pair_value_compare const& other) : Compare(other)
{
}
private:
friend aged_ordered_container;
pair_value_compare(Compare const& compare)
: beast::detail::empty_base_optimization<Compare>(compare)
pair_value_compare(Compare const& compare) : Compare(compare)
{
}
};
// Compares value_type against element, used in insert_check
// VFALCO TODO hoist to remove template argument dependencies
class KeyValueCompare
: public beast::detail::empty_base_optimization<Compare>
#ifdef _LIBCPP_VERSION
,
public std::binary_function<Key, element, bool>
#endif
class KeyValueCompare : public Compare
{
public:
#ifndef _LIBCPP_VERSION
using first_argument = Key;
using second_argument = element;
using result_type = bool;
#endif
KeyValueCompare() = default;
KeyValueCompare(Compare const& compare)
: beast::detail::empty_base_optimization<Compare>(compare)
KeyValueCompare(Compare const& compare) : Compare(compare)
{
}
// VFALCO NOTE WE might want only to enable these overloads
// if Compare has is_transparent
#if 0
template <class K>
bool operator() (K const& k, element const& e) const
{
return this->member() (k, extract (e.value));
}
template <class K>
bool operator() (element const& e, K const& k) const
{
return this->member() (extract (e.value), k);
}
#endif
bool
operator()(Key const& k, element const& e) const
{
return this->member()(k, extract(e.value));
return Compare::operator()(k, extract(e.value));
}
bool
operator()(element const& e, Key const& k) const
{
return this->member()(extract(e.value), k);
return Compare::operator()(extract(e.value), k);
}
bool
operator()(element const& x, element const& y) const
{
return this->member()(extract(x.value), extract(y.value));
return Compare::operator()(extract(x.value), extract(y.value));
}
Compare&
compare()
{
return beast::detail::empty_base_optimization<Compare>::member();
return *this;
}
Compare const&
compare() const
{
return beast::detail::empty_base_optimization<Compare>::member();
return *this;
}
};

View File

@@ -148,115 +148,84 @@ private:
};
// VFALCO TODO hoist to remove template argument dependencies
class ValueHash : private beast::detail::empty_base_optimization<Hash>
#ifdef _LIBCPP_VERSION
,
public std::unary_function<element, std::size_t>
#endif
class ValueHash : public Hash
{
public:
#ifndef _LIBCPP_VERSION
using argument_type = element;
using result_type = size_t;
#endif
ValueHash()
{
}
ValueHash(Hash const& h)
: beast::detail::empty_base_optimization<Hash>(h)
ValueHash(Hash const& h) : Hash(h)
{
}
std::size_t
operator()(element const& e) const
{
return this->member()(extract(e.value));
return Hash::operator()(extract(e.value));
}
Hash&
hash_function()
{
return this->member();
return *this;
}
Hash const&
hash_function() const
{
return this->member();
return *this;
}
};
// Compares value_type against element, used in find/insert_check
// VFALCO TODO hoist to remove template argument dependencies
class KeyValueEqual
: private beast::detail::empty_base_optimization<KeyEqual>
#ifdef _LIBCPP_VERSION
,
public std::binary_function<Key, element, bool>
#endif
class KeyValueEqual : public KeyEqual
{
public:
#ifndef _LIBCPP_VERSION
using first_argument_type = Key;
using second_argument_type = element;
using result_type = bool;
#endif
KeyValueEqual()
{
}
KeyValueEqual(KeyEqual const& keyEqual)
: beast::detail::empty_base_optimization<KeyEqual>(keyEqual)
KeyValueEqual(KeyEqual const& keyEqual) : KeyEqual(keyEqual)
{
}
// VFALCO NOTE WE might want only to enable these overloads
// if KeyEqual has is_transparent
#if 0
template <class K>
bool operator() (K const& k, element const& e) const
{
return this->member() (k, extract (e.value));
}
template <class K>
bool operator() (element const& e, K const& k) const
{
return this->member() (extract (e.value), k);
}
#endif
bool
operator()(Key const& k, element const& e) const
{
return this->member()(k, extract(e.value));
return KeyEqual::operator()(k, extract(e.value));
}
bool
operator()(element const& e, Key const& k) const
{
return this->member()(extract(e.value), k);
return KeyEqual::operator()(extract(e.value), k);
}
bool
operator()(element const& lhs, element const& rhs) const
{
return this->member()(extract(lhs.value), extract(rhs.value));
return KeyEqual::operator()(extract(lhs.value), extract(rhs.value));
}
KeyEqual&
key_eq()
{
return this->member();
return *this;
}
KeyEqual const&
key_eq() const
{
return this->member();
return *this;
}
};

View File

@@ -277,7 +277,7 @@ public:
Keylet const& key,
std::function<void(std::shared_ptr<SLE> const&)> const& describe)
{
if (key.type != ltOFFER)
if (key.type != ltOFFER && key.type != ltOPTION_OFFER)
{
assert(!"Only Offers are appended to book directories. "
"Call dirInsert() instead.");

View File

@@ -19,6 +19,14 @@
#include <ripple/basics/contract.h>
#include <ripple/ledger/detail/ApplyViewBase.h>
#include <ripple/protocol/STAccount.h>
// #include <ripple/app/paths/impl/AmountSpec.h>
// #include <ripple/ledger/PaymentSandbox.h>
// #include <ripple/ledger/View.h>
// #include <ripple/protocol/Feature.h>
// #include <ripple/protocol/SField.h>
// #include <ripple/protocol/STAccount.h>
namespace ripple {
namespace detail {

View File

@@ -91,17 +91,10 @@ private:
using value_type = map_type::value_type;
struct Transform
#ifdef _LIBCPP_VERSION
: std::unary_function<
map_type::right_map::const_iterator::value_type const&,
beast::IP::Endpoint const&>
#endif
{
#ifndef _LIBCPP_VERSION
using first_argument_type =
map_type::right_map::const_iterator::value_type const&;
using result_type = beast::IP::Endpoint const&;
#endif
explicit Transform() = default;

View File

@@ -69,14 +69,9 @@ public:
public:
// Iterator transformation to extract the endpoint from Element
struct Transform
#ifdef _LIBCPP_VERSION
: public std::unary_function<Element, Endpoint>
#endif
{
#ifndef _LIBCPP_VERSION
using first_argument = Element;
using result_type = Endpoint;
#endif
explicit Transform() = default;
@@ -239,15 +234,9 @@ public:
template <bool IsConst>
struct Transform
#ifdef _LIBCPP_VERSION
: public std::
unary_function<typename lists_type::value_type, Hop<IsConst>>
#endif
{
#ifndef _LIBCPP_VERSION
using first_argument = typename lists_type::value_type;
using result_type = Hop<IsConst>;
#endif
explicit Transform() = default;

View File

@@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 70;
static constexpr std::size_t numFeatures = 71;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -358,6 +358,7 @@ extern uint256 const fixXahauV2;
extern uint256 const featureRemit;
extern uint256 const featureZeroB2M;
extern uint256 const fixNSDelete;
extern uint256 const featureOptions;
} // namespace ripple

View File

@@ -297,6 +297,24 @@ import_vlseq(PublicKey const& key) noexcept;
Keylet
uritoken(AccountID const& issuer, Blob const& uri);
Keylet
option(AccountID const& issuer, Currency const& currency, std::uint64_t strike, std::uint32_t expiration) noexcept;
Keylet
optionBook(AccountID const& issuer, Currency const& currency, std::uint64_t strike, std::uint32_t expiration) noexcept;
Keylet
optionOffer(AccountID const& id, std::uint32_t seq) noexcept;
inline Keylet
optionOffer(uint256 const& key) noexcept
{
return {ltOPTION_OFFER, key};
}
Keylet
optionQuality(Keylet const& k, std::uint64_t q) noexcept;
} // namespace keylet
// Everything below is deprecated and should be removed in favor of keylets:
@@ -311,6 +329,18 @@ getQualityNext(uint256 const& uBase);
std::uint64_t
getQuality(uint256 const& uBase);
uint256
getOptionBookBase(AccountID const& issuer, Currency const& currency, std::uint64_t strike, std::uint32_t expiration);
uint256
getOptionQualityNext(uint256 const& uBase);
// VFALCO This name could be better
std::uint64_t
getOptionQuality(uint256 const& uBase);
/** The initial directory page for a specific quality */
uint256
getTicketIndex(AccountID const& account, std::uint32_t uSequence);

View File

@@ -254,6 +254,18 @@ enum LedgerEntryType : std::uint16_t
\sa keylet::emitted
*/
ltEMITTED_TXN = 'E',
/** A ledger object
\sa keylet::broker_state
*/
ltOPTION = 'X',
/** A ledger object
\sa keylet::broker_state
*/
ltOPTION_OFFER = 'y',
};
// clang-format off
@@ -315,6 +327,12 @@ enum LedgerSpecificFlags {
// ltURI_TOKEN
lsfBurnable = 0x00000001, // True, issuer can burn the token
// ltOPTION_OFFER
lsfType = 0x00010000,
lsfAction = 0x00020000,
lsfPosition = 0x00040000,
lsfCovered = 0x00080000,
};
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,103 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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_PROTOCOL_OPTION_H_INCLUDED
#define RIPPLE_PROTOCOL_OPTION_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <ripple/protocol/Issue.h>
#include <boost/utility/base_from_member.hpp>
namespace ripple {
class Option final : public CountedObject<Option>
{
public:
Issue issue;
uint64_t strike;
uint32_t expiration;
Option()
{
}
Option(
Issue issue_,
uint64_t strike_,
uint32_t expiration_)
: issue(issue_)
, strike(strike_)
, expiration(expiration_)
{
}
};
std::string
to_string(Option const& option);
std::ostream&
operator<<(std::ostream& os, Option const& x);
template <class Hasher>
void
hash_append(Hasher& h, Option const& o)
{
using beast::hash_append;
hash_append(h, o.issue, o.strike, o.expiration);
}
/** Equality comparison. */
/** @{ */
[[nodiscard]] inline constexpr bool
operator==(Option const& lhs, Option const& rhs)
{
return (lhs.issue == rhs.issue) && (lhs.strike == rhs.strike) && (lhs.expiration == rhs.expiration);
}
/** @} */
/** Strict weak ordering. */
/** @{ */
[[nodiscard]] inline constexpr std::weak_ordering
operator<=>(Option const& lhs, Option const& rhs)
{
if (auto const c{lhs.strike <=> rhs.strike}; c != 0)
return c;
return lhs.strike <=> rhs.strike;
}
/** @} */
} // namespace ripple
//------------------------------------------------------------------------------
namespace boost {
template <>
struct hash<ripple::Option> : std::hash<ripple::Option>
{
explicit hash() = default;
using Base = std::hash<ripple::Option>;
// VFALCO NOTE broken in vs2012
// using Base::Base; // inherit ctors
};
} // namespace boost
#endif

View File

@@ -410,6 +410,8 @@ extern SF_UINT32 const sfRewardLgrLast;
extern SF_UINT32 const sfFirstNFTokenSequence;
extern SF_UINT32 const sfImportSequence;
extern SF_UINT32 const sfXahauActivationLgrSeq;
extern SF_UINT32 const sfQuantity;
extern SF_UINT32 const sfOpenInterest;
// 64-bit integers (common)
extern SF_UINT64 const sfIndexNext;
@@ -442,6 +444,7 @@ extern SF_UINT160 const sfTakerPaysCurrency;
extern SF_UINT160 const sfTakerPaysIssuer;
extern SF_UINT160 const sfTakerGetsCurrency;
extern SF_UINT160 const sfTakerGetsIssuer;
extern SF_UINT160 const sfCurrency;
// 256-bit (common)
extern SF_UINT256 const sfLedgerHash;
@@ -483,6 +486,8 @@ extern SF_UINT256 const sfURITokenID;
extern SF_UINT256 const sfGovernanceFlags;
extern SF_UINT256 const sfGovernanceMarks;
extern SF_UINT256 const sfEmittedTxnID;
extern SF_UINT256 const sfOptionID;
extern SF_UINT256 const sfSwapID;
// currency amount (common)
extern SF_AMOUNT const sfAmount;
@@ -496,6 +501,7 @@ extern SF_AMOUNT const sfFee;
extern SF_AMOUNT const sfSendMax;
extern SF_AMOUNT const sfDeliverMin;
extern SF_AMOUNT const sfLockedBalance;
extern SF_AMOUNT const sfStrikePrice;
// currency amount (uncommon)
extern SF_AMOUNT const sfMinimumOffer;

View File

@@ -171,8 +171,7 @@ STValidation::STValidation(
{
if (checkSignature && !isValid())
{
JLOG(debugLog().error()) << "Invalid signature in validation: "
<< getJson(JsonOptions::none);
JLOG(debugLog().error()) << "Invalid signature in validation";
Throw<std::runtime_error>("Invalid signature in validation");
}

View File

@@ -190,6 +190,21 @@ enum ClaimRewardFlags : uint32_t {
tfOptOut = 0x00000001,
};
// OptionCreate flags:
enum OptionCreateFlags : uint32_t {
tfType = 0x00010000, // 0 Call 1 Put
tfAction = 0x00020000, // 0 Buy 2 Sell
tfPosition = 0x00040000, // 0 Open 4 Close
};
constexpr std::uint32_t tfOptionCreateMask =
~(tfUniversal | tfType | tfAction | tfPosition);
// OptionExpire flags:
enum OptionExpireFlags : uint32_t {
tfOptionExpire = 0x00010000,
};
constexpr std::uint32_t tfOptionExpireMask = ~(tfUniversal | tfOptionExpire);
// clang-format on
} // namespace ripple

View File

@@ -185,6 +185,9 @@ enum TxType : std::uint16_t
ttUNL_MODIFY = 102,
ttEMIT_FAILURE = 103,
ttUNL_REPORT = 104,
ttOPTION_LIST = 105,
ttOPTION_CREATE = 106,
ttOPTION_EXERCISE = 107,
};
// clang-format on

View File

@@ -464,6 +464,7 @@ REGISTER_FIX (fixXahauV2, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixNSDelete, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Options, Supported::yes, VoteBehavior::DefaultNo);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -72,6 +72,9 @@ enum class LedgerNameSpace : std::uint16_t {
URI_TOKEN = 'U',
IMPORT_VLSEQ = 'I',
UNL_REPORT = 'R',
OPTION = 'X',
OPTION_DIR = 'Y',
OPTION_OFFER = 'y',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
@@ -120,6 +123,33 @@ getQuality(uint256 const& uBase)
return boost::endian::big_to_native(((std::uint64_t*)uBase.end())[-1]);
}
uint256
getOptionBookBase(AccountID const& issuer, Currency const& currency, std::uint64_t strike, std::uint32_t expiration)
{
auto const index = indexHash(
LedgerNameSpace::BOOK_DIR,
issuer, currency, strike, expiration);
// Return with quality 0.
auto k = keylet::optionQuality({ltDIR_NODE, index}, 0);
return k.key;
}
uint256
getOptionQualityNext(uint256 const& uBase)
{
static constexpr uint256 nextq(
"0000000000000000000000000000000000000000000000010000000000000000");
return uBase + nextq;
}
std::uint64_t
getOptionQuality(uint256 const& uBase)
{
// VFALCO [base_uint] This assumes a certain storage format
return boost::endian::big_to_native(((std::uint64_t*)uBase.end())[-1]);
}
uint256
getTicketIndex(AccountID const& account, std::uint32_t ticketSeq)
{
@@ -443,6 +473,42 @@ uritoken(AccountID const& issuer, Blob const& uri)
LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})};
}
Keylet
option(AccountID const& issuer, Currency const& currency, std::uint64_t strike, std::uint32_t expiration) noexcept
{
return {ltOPTION, indexHash(LedgerNameSpace::OPTION, issuer, currency, strike, expiration)};
}
Keylet
optionBook(AccountID const& issuer, Currency const& currency, std::uint64_t strike, std::uint32_t expiration) noexcept
{
return {ltDIR_NODE, getOptionBookBase(issuer, currency, strike, expiration)};
}
Keylet
optionQuality(Keylet const& k, std::uint64_t q) noexcept
{
assert(k.type == ltDIR_NODE);
// 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 {ltDIR_NODE, x};
}
Keylet
optionOffer(AccountID const& id, std::uint32_t seq) noexcept
{
return {ltOPTION_OFFER, indexHash(LedgerNameSpace::OPTION_OFFER, id, seq)};
}
} // namespace keylet
} // namespace ripple

View File

@@ -84,6 +84,8 @@ LedgerFormats::LedgerFormats()
{sfIndexNext, soeOPTIONAL},
{sfIndexPrevious, soeOPTIONAL},
{sfNFTokenID, soeOPTIONAL},
{sfBaseFee, soeOPTIONAL},
{sfExpiration, soeOPTIONAL},
},
commonFields);
@@ -363,6 +365,38 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::Option,
ltOPTION,
{
{sfOwnerNode, soeREQUIRED},
{sfStrikePrice, soeREQUIRED},
{sfIssuer, soeREQUIRED},
{sfCurrency, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}
},
commonFields);
add(jss::OptionOffer,
ltOPTION_OFFER,
{
{sfOwner, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfOptionID, soeREQUIRED}, // OptionID
{sfLockedBalance, soeREQUIRED}, // Locked Amount
{sfAmount, soeREQUIRED}, // Premium
{sfQuantity, soeREQUIRED}, // Quantity
{sfOpenInterest, soeREQUIRED}, // To Seal
{sfBookDirectory, soeREQUIRED},
{sfBookNode, soeREQUIRED},
{sfSwapID, soeOPTIONAL}, // MatchID
{sfCheckID, soeOPTIONAL}, // SwapID
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}
},
commonFields);
// clang-format on
}

View File

@@ -0,0 +1,37 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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/protocol/Option.h>
namespace ripple {
std::string
to_string(Option const& option)
{
return to_string(option.issue) + "/" + to_string(option.strike) + "/" + to_string(option.expiration);
}
std::ostream&
operator<<(std::ostream& os, Option const& x)
{
os << to_string(x);
return os;
}
} // namespace ripple

View File

@@ -157,6 +157,8 @@ CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32,
CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50);
CONSTRUCT_TYPED_SFIELD(sfOpenInterest, "OpenInterest", UINT32, 94);
CONSTRUCT_TYPED_SFIELD(sfQuantity, "Quantity", UINT32, 95);
CONSTRUCT_TYPED_SFIELD(sfXahauActivationLgrSeq, "XahauActivationLgrSeq",UINT32, 96);
CONSTRUCT_TYPED_SFIELD(sfImportSequence, "ImportSequence", UINT32, 97);
CONSTRUCT_TYPED_SFIELD(sfRewardTime, "RewardTime", UINT32, 98);
@@ -195,6 +197,7 @@ CONSTRUCT_TYPED_SFIELD(sfTakerPaysCurrency, "TakerPaysCurrency", UINT160,
CONSTRUCT_TYPED_SFIELD(sfTakerPaysIssuer, "TakerPaysIssuer", UINT160, 2);
CONSTRUCT_TYPED_SFIELD(sfTakerGetsCurrency, "TakerGetsCurrency", UINT160, 3);
CONSTRUCT_TYPED_SFIELD(sfTakerGetsIssuer, "TakerGetsIssuer", UINT160, 4);
CONSTRUCT_TYPED_SFIELD(sfCurrency, "Currency", UINT160, 5);
// 256-bit (common)
CONSTRUCT_TYPED_SFIELD(sfLedgerHash, "LedgerHash", UINT256, 1);
@@ -236,6 +239,8 @@ CONSTRUCT_TYPED_SFIELD(sfURITokenID, "URITokenID", UINT256,
CONSTRUCT_TYPED_SFIELD(sfGovernanceFlags, "GovernanceFlags", UINT256, 99);
CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, 98);
CONSTRUCT_TYPED_SFIELD(sfEmittedTxnID, "EmittedTxnID", UINT256, 97);
CONSTRUCT_TYPED_SFIELD(sfOptionID, "OptionID", UINT256, 96);
CONSTRUCT_TYPED_SFIELD(sfSwapID, "SwapID", UINT256, 95);
// currency amount (common)
CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
@@ -261,6 +266,7 @@ CONSTRUCT_TYPED_SFIELD(sfLockedBalance, "LockedBalance", AMOUNT,
CONSTRUCT_TYPED_SFIELD(sfBaseFeeDrops, "BaseFeeDrops", AMOUNT, 22);
CONSTRUCT_TYPED_SFIELD(sfReserveBaseDrops, "ReserveBaseDrops", AMOUNT, 23);
CONSTRUCT_TYPED_SFIELD(sfReserveIncrementDrops, "ReserveIncrementDrops", AMOUNT, 24);
CONSTRUCT_TYPED_SFIELD(sfStrikePrice, "StrikePrice", AMOUNT, 25);
// variable length (common)
CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1);

View File

@@ -456,6 +456,37 @@ TxFormats::TxFormats()
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::OptionList,
ttOPTION_LIST,
{
{sfStrikePrice, soeREQUIRED},
{sfIssuer, soeREQUIRED},
{sfCurrency, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::OptionCreate,
ttOPTION_CREATE,
{
{sfOptionID, soeREQUIRED}, // Option ID
{sfAmount, soeREQUIRED}, // Premium
{sfQuantity, soeREQUIRED}, // Quantity
{sfSwapID, soeOPTIONAL}, // Swap ID
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::OptionExercise,
ttOPTION_EXERCISE,
{
{sfOptionID, soeREQUIRED},
{sfSwapID, soeOPTIONAL},
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
}
TxFormats const&

View File

@@ -111,8 +111,13 @@ JSS(Offer); // ledger type.
JSS(OfferCancel); // transaction type.
JSS(OfferCreate); // transaction type.
JSS(OfferSequence); // field.
JSS(Option); // ledger type.
JSS(OptionCreate); // transaction type.
JSS(OptionExercise); // transaction type.
JSS(OptionList); // transaction type.
JSS(OptionOffer); // ledger type.
JSS(Paths); // in/out: TransactionSign
JSS(PayChannel); // ledger type.
JSS(PayChannel); // ledger type.E
JSS(Payment); // transaction type.
JSS(PaymentChannelClaim); // transaction type.
JSS(PaymentChannelCreate); // transaction type.
@@ -510,6 +515,7 @@ JSS(open); // out: handlers/Ledger
JSS(open_ledger_cost); // out: SubmitTransaction
JSS(open_ledger_fee); // out: TxQ
JSS(open_ledger_level); // out: TxQ
JSS(option); // out: AccountObjects
JSS(owner); // in: LedgerEntry, out: NetworkOPs
JSS(owner_funds); // in/out: Ledger, NetworkOPs, AcceptedLedgerTx
JSS(page_index);
@@ -630,6 +636,7 @@ JSS(stop_history_tx_only); // in: Unsubscribe, stop history tx stream
JSS(storedSeqs); // out: NodeToShardStatus
JSS(streams); // in: Subscribe, Unsubscribe
JSS(strict); // in: AccountCurrencies, AccountInfo
JSS(strike_price); // in: NetworkOPs
JSS(sub_index); // in: LedgerEntry
JSS(subcommand); // in: PathFind
JSS(success); // rpc

View File

@@ -189,8 +189,8 @@ doAccountObjects(RPC::JsonContext& context)
}
}
if (!ledger->exists(keylet::account(accountID)))
return rpcError(rpcACT_NOT_FOUND);
// if (!ledger->exists(keylet::account(accountID)))
// return rpcError(rpcACT_NOT_FOUND);
std::optional<std::vector<LedgerEntryType>> typeFilter;
@@ -208,6 +208,7 @@ doAccountObjects(RPC::JsonContext& context)
{jss::hook, ltHOOK},
{jss::payment_channel, ltPAYCHAN},
{jss::uri_token, ltURI_TOKEN},
{jss::option, ltOPTION},
{jss::state, ltRIPPLE_STATE}};
typeFilter.emplace();

View File

@@ -103,6 +103,8 @@ doNodeToShard(RPC::JsonContext&);
Json::Value
doNoRippleCheck(RPC::JsonContext&);
Json::Value
doOptionBookOffers(RPC::JsonContext&);
Json::Value
doOwnerInfo(RPC::JsonContext&);
Json::Value
doPathFind(RPC::JsonContext&);

View File

@@ -0,0 +1,160 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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.
*/
//==============================================================================
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/ReadView.h>
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/jss.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace ripple {
Json::Value
doOptionBookOffers(RPC::JsonContext& context)
{
// VFALCO TODO Here is a terrible place for this kind of business
// logic. It needs to be moved elsewhere and documented,
// and encapsulated into a function.
if (context.app.getJobQueue().getJobCountGE(jtCLIENT) > 200)
return rpcError(rpcTOO_BUSY);
std::shared_ptr<ReadView const> lpLedger;
auto jvResult = RPC::lookupLedger(lpLedger, context);
if (!lpLedger)
return jvResult;
if (!context.params.isMember(jss::strike_price))
return RPC::missing_field_error(jss::strike_price);
Json::Value const& strike_price = context.params[jss::strike_price];
if (!strike_price.isObjectOrNull())
return RPC::object_field_error(jss::strike_price);
// if (!taker_gets.isObjectOrNull())
// return RPC::object_field_error(jss::taker_gets);
if (!strike_price.isMember(jss::currency))
return RPC::missing_field_error("strike_price.currency");
if (!strike_price[jss::currency].isString())
return RPC::expected_field_error("strike_price.currency", "string");
// if (!taker_gets.isMember(jss::currency))
// return RPC::missing_field_error("taker_gets.currency");
// if (!taker_gets[jss::currency].isString())
// return RPC::expected_field_error("taker_gets.currency", "string");
Currency pay_currency;
if (!to_currency(pay_currency, strike_price[jss::currency].asString()))
{
JLOG(context.j.info()) << "Bad strike_price currency.";
return RPC::make_error(
rpcSRC_CUR_MALFORMED,
"Invalid field 'strike_price.currency', bad currency.");
}
AccountID pay_issuer;
if (strike_price.isMember(jss::issuer))
{
if (!strike_price[jss::issuer].isString())
return RPC::expected_field_error("strike_price.issuer", "string");
if (!to_issuer(pay_issuer, strike_price[jss::issuer].asString()))
return RPC::make_error(
rpcSRC_ISR_MALFORMED,
"Invalid field 'strike_price.issuer', bad issuer.");
if (pay_issuer == noAccount())
return RPC::make_error(
rpcSRC_ISR_MALFORMED,
"Invalid field 'strike_price.issuer', bad issuer account one.");
}
else
{
pay_issuer = xrpAccount();
}
std::optional<std::uint64_t> pay_value;
if (strike_price.isMember(jss::value))
{
if (!strike_price[jss::value].isString())
return RPC::expected_field_error("strike_price.value", "string");
pay_value = strike_price[jss::value].asInt();
if (!pay_value)
return RPC::invalid_field_error(jss::value);
}
if (isXRP(pay_currency) && !isXRP(pay_issuer))
return RPC::make_error(
rpcSRC_ISR_MALFORMED,
"Unneeded field 'taker_pays.issuer' for "
"XRP currency specification.");
if (!isXRP(pay_currency) && isXRP(pay_issuer))
return RPC::make_error(
rpcSRC_ISR_MALFORMED,
"Invalid field 'taker_pays.issuer', expected non-XRP issuer.");
std::optional<std::uint32_t> expiration;
if (context.params.isMember(jss::expiration))
{
if (!context.params[jss::expiration].isString())
return RPC::expected_field_error(jss::expiration, "string");
expiration = context.params[jss::expiration].asInt();
if (!expiration)
return RPC::invalid_field_error(jss::expiration);
}
unsigned int limit;
if (auto err = readLimitField(limit, RPC::Tuning::bookOffers, context))
return *err;
bool const bProof(context.params.isMember(jss::proof));
Json::Value const jvMarker(
context.params.isMember(jss::marker) ? context.params[jss::marker]
: Json::Value(Json::nullValue));
context.netOps.getOptionBookPage(
lpLedger,
STAmount(Issue(pay_currency, pay_issuer), pay_value ? *pay_value : 0),
expiration ? *expiration : 0,
limit,
jvMarker,
jvResult);
context.loadType = Resource::feeMediumBurdenRPC;
return jvResult;
}
} // namespace ripple

View File

@@ -19,6 +19,7 @@
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/reporting/P2pProxy.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/Role.h>

View File

@@ -118,6 +118,7 @@ Handler const handlerArray[]{
{"nft_sell_offers", byRef(&doNFTSellOffers), Role::USER, NO_CONDITION},
{"node_to_shard", byRef(&doNodeToShard), Role::ADMIN, NO_CONDITION},
{"noripple_check", byRef(&doNoRippleCheck), Role::USER, NO_CONDITION},
{"option_book_offers", byRef(&doOptionBookOffers), Role::USER, NO_CONDITION},
{"owner_info", byRef(&doOwnerInfo), Role::USER, NEEDS_CURRENT_LEDGER},
{"peers", byRef(&doPeers), Role::ADMIN, NO_CONDITION},
{"path_find", byRef(&doPathFind), Role::USER, NEEDS_CURRENT_LEDGER},

View File

@@ -1067,7 +1067,7 @@ chooseLedgerEntryType(Json::Value const& params)
std::pair<RPC::Status, LedgerEntryType> result{RPC::Status::OK, ltANY};
if (params.isMember(jss::type))
{
static constexpr std::array<std::pair<char const*, LedgerEntryType>, 22>
static constexpr std::array<std::pair<char const*, LedgerEntryType>, 23>
types{
{{jss::account, ltACCOUNT_ROOT},
{jss::amendments, ltAMENDMENTS},
@@ -1083,6 +1083,7 @@ chooseLedgerEntryType(Json::Value const& params)
{jss::hashes, ltLEDGER_HASHES},
{jss::import_vlseq, ltIMPORT_VLSEQ},
{jss::offer, ltOFFER},
{jss::option, ltOPTION},
{jss::payment_channel, ltPAYCHAN},
{jss::uri_token, ltURI_TOKEN},
{jss::signer_list, ltSIGNER_LIST},

View File

@@ -398,7 +398,7 @@ SHAMapInnerNode::canonicalizeChild(
void
SHAMapInnerNode::invariants(bool is_root) const
{
unsigned count = 0;
[[maybe_unused]] unsigned count = 0;
auto [numAllocated, hashes, children] =
hashesAndChildren_.getHashesAndChildren();

View File

@@ -17,7 +17,9 @@
*/
//==============================================================================
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
namespace ripple {

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
namespace ripple {

View File

@@ -0,0 +1,621 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2016 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/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
namespace ripple {
namespace test {
struct Option_test : public beast::unit_test::suite
{
// testDebug("PRE", env, { alice, bob }, {});
void
testDebug(
std::string const& testNumber,
jtx::Env const& env,
std::vector<jtx::Account> const& accounts,
std::vector<jtx::IOU> const& ious)
{
std::cout << "DEBUG: " << testNumber << "\n";
for (std::size_t a = 0; a < accounts.size(); ++a)
{
auto const bal = env.balance(accounts[a]);
std::cout << "account: " << accounts[a].human() << "BAL: " << bal
<< "\n";
for (std::size_t i = 0; i < ious.size(); ++i)
{
auto const iouBal = env.balance(accounts[a], ious[i]);
std::cout << "account: " << accounts[a].human()
<< "IOU: " << iouBal << "\n";
}
}
}
static STAmount
lockedValue(
jtx::Env const& env,
jtx::Account const& account,
std::uint32_t const& seq)
{
auto const sle = env.le(keylet::optionOffer(account, seq));
if (sle->isFieldPresent(sfLockedBalance))
return (*sle)[sfLockedBalance];
return STAmount(0);
}
Json::Value
optionlist(
jtx::Account const& account,
std::uint32_t expiration,
STAmount const& strikePrice,
STAmount const& amount)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::OptionList;
jv[jss::Account] = account.human();
jv[sfStrikePrice.jsonName] = strikePrice.getJson(JsonOptions::none);
jv[sfIssuer.jsonName] = to_string(amount.getIssuer());
jv[sfCurrency.jsonName] = to_string(amount.getCurrency());
jv[sfExpiration.jsonName] = expiration;
return jv;
}
Json::Value
optioncreate(
jtx::Account const& account,
uint256 const& optionId,
uint32_t const& quantity,
STAmount const& premium)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::OptionCreate;
jv[jss::Account] = account.human();
jv[sfOptionID.jsonName] = to_string(optionId);
jv[jss::Amount] = premium.getJson(JsonOptions::none);
jv[sfQuantity.jsonName] = quantity;
return jv;
}
Json::Value
optionexecute(
jtx::Account const& account,
uint256 const& optionId,
uint256 const& offerId)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::OptionExercise;
jv[jss::Account] = account.human();
jv[sfOptionID.jsonName] = to_string(optionId);
jv[sfSwapID.jsonName] = to_string(offerId);
;
return jv;
}
static uint256
getOptionIndex(
AccountID const& issuer,
Currency const& currency,
std::uint64_t const& strike,
std::uint32_t expiration)
{
return keylet::option(issuer, currency, strike, expiration).key;
}
static uint256
getOfferIndex(AccountID const& account, std::uint32_t sequence)
{
return keylet::optionOffer(account, sequence).key;
}
static auto
getOptionList(
jtx::Env& env,
AccountID const& issuer)
{
Json::Value jvbp;
jvbp[jss::ledger_index] = "current";
jvbp[jss::account] = to_string(issuer);
jvbp[jss::type] = "option";
return env.rpc("json", "account_objects", to_string(jvbp))[jss::result];
}
static auto
getOptionBookOffers(
jtx::Env& env,
STAmount const& strike_price,
std::uint32_t expiration)
{
Json::Value jvbp;
jvbp[jss::ledger_index] = "current";
jvbp[jss::strike_price][jss::currency] = to_string(strike_price.issue().currency);
jvbp[jss::strike_price][jss::issuer] = to_string(strike_price.issue().account);
jvbp[jss::strike_price][jss::value] = to_string(strike_price.mantissa());
jvbp[jss::expiration] = to_string(expiration);
return env.rpc("json", "option_book_offers", to_string(jvbp))[jss::result];
}
// void
// testBookBuy(FeatureBitset features)
// {
// testcase("book buy");
// using namespace test::jtx;
// using namespace std::literals;
// test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
// auto const alice = Account("alice");
// auto const bob = Account("bob");
// auto const gw = Account("gw");
// IOU const USD(gw["USD"]);
// env.fund(XRP(1000), gw, alice, bob);
// env.close();
// env.trust(USD(100000), alice, bob);
// env.close();
// env(pay(gw, alice, USD(10000)));
// env(pay(gw, bob, USD(10000)));
// env.close();
// // Alice offers to sell 100 XRP for 110 USD.
// // Create an sell: TakerPays 100, TakerGets 110/USD
// env(offer(alice, XRP(100), USD(110)), txflags(tfSell)); // <- Best
// // Alice offers to sell 100 XRP for 100 USD.
// // Create an sell: TakerPays 100, TakerGets 100/USD
// env(offer(alice, XRP(100), USD(100)), txflags(tfSell));
// // Alice offers to sell 100 XRP for 90 USD.
// // Create an sell: TakerPays 100, TakerGets 90/USD
// env(offer(alice, XRP(100), USD(90)), txflags(tfSell));
// // Alice offers to sell 100 XRP for 80 USD.
// // Create an sell: TakerPays 100, TakerGets 80/USD
// env(offer(alice, XRP(100), USD(80)), txflags(tfSell));
// // Alice offers to sell 100 XRP for 70 USD.
// // Create an sell: TakerPays 100, TakerGets 70/USD
// env(offer(alice, XRP(100), USD(70)), txflags(tfSell)); // <- Worst
// env.close();
// // Bob offers to buy 110 USD for 100 XRP.
// // env(offer(bob, USD(110), XRP(100))); // <- Best
// Book const book1{
// xrpIssue(),
// USD.issue(),
// };
// const uint256 uBookBase1 = getBookBase(book1);
// const uint256 uBookEnd1 = getQualityNext(uBookBase1);
// auto view1 = env.closed();
// auto key1 = view1->succ(uBookBase1, uBookEnd1);
// if (key1)
// {
// auto sleOfferDir1 = view1->read(keylet::page(key1.value()));
// uint256 offerIndex1;
// unsigned int bookEntry1;
// cdirFirst(
// *view1,
// sleOfferDir1->key(),
// sleOfferDir1,
// bookEntry1,
// offerIndex1);
// auto sleOffer1 = view1->read(keylet::offer(offerIndex1));
// auto const dir1 =
// to_string(sleOffer1->getFieldH256(sfBookDirectory));
// auto const uTipIndex1 = sleOfferDir1->key();
// STAmount dirRate1 = amountFromQuality(getQuality(uTipIndex1));
// auto const rate1 = dirRate1 / 1'000'000;
// std::cout << "dirRate1: " << dirRate1 << "\n";
// std::cout << "rate1: " << rate1 << "\n";
// std::cout << "rate1=: " << (100 / rate1) << "\n";
// BEAST_EXPECT(100 / rate1 == 110);
// }
// }
// void
// testBookSell(FeatureBitset features)
// {
// testcase("book sell");
// using namespace test::jtx;
// using namespace std::literals;
// test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
// auto const alice = Account("alice");
// auto const bob = Account("bob");
// auto const gw = Account("gw");
// IOU const USD(gw["USD"]);
// env.fund(XRP(1000), gw, alice, bob);
// env.close();
// env.trust(USD(100000), alice, bob);
// env.close();
// env(pay(gw, alice, USD(10000)));
// env(pay(gw, bob, USD(10000)));
// env.close();
// // Bob offers to buy 70 XRP for 100 USD.
// // Create an buy: TakerPays 100/USD, TakerGets 70
// env(offer(bob, USD(100), XRP(70))); // <- Worst
// // Bob offers to buy 80 XRP for 100 USD.
// // Create an buy: TakerPays 100/USD, TakerGets 80
// env(offer(bob, USD(100), XRP(80)));
// // Bob offers to buy 90 XRP for 100 USD.
// // Create an buy: TakerPays 100/USD, TakerGets 90
// env(offer(bob, USD(100), XRP(90)));
// // Bob offers to buy 100 XRP for 100 USD.
// // Create an buy: TakerPays 100/USD, TakerGets 100
// env(offer(bob, USD(100), XRP(100)));
// // Bob offers to buy 110 XRP for 100 USD.
// // Create an buy: TakerPays 100/USD, TakerGets 110
// env(offer(bob, USD(100), XRP(110))); // <- Best
// env.close();
// // Alice offers to sell 110 XRP for 100 USD.
// // env(offer(alice, XRP(110), USD(100))); // <- Best
// Book const bookOpp{
// USD.issue(),
// xrpIssue(),
// };
// const uint256 uBookBaseOpp = getBookBase(bookOpp);
// const uint256 uBookEndOpp = getQualityNext(uBookBaseOpp);
// auto view = env.closed();
// auto key = view->succ(uBookBaseOpp, uBookEndOpp);
// if (key)
// {
// auto sleOfferDir = view->read(keylet::page(key.value()));
// uint256 offerIndex;
// unsigned int bookEntry;
// cdirFirst(
// *view, sleOfferDir->key(), sleOfferDir, bookEntry, offerIndex);
// auto sleOffer = view->read(keylet::offer(offerIndex));
// auto const dir = to_string(sleOffer->getFieldH256(sfBookDirectory));
// auto const uTipIndex = sleOfferDir->key();
// STAmount dirRate = amountFromQuality(getQuality(uTipIndex));
// auto const rate = dirRate * 1'000'000;
// std::cout << "dirRate: " << dirRate << "\n";
// std::cout << "rate: " << rate << "\n";
// std::cout << "rate=: " << (100 / rate) << "\n";
// BEAST_EXPECT(100 / rate == 110);
// }
// }
// void
// testOptionBookBuy(FeatureBitset features)
// {
// testcase("option book buy");
// using namespace test::jtx;
// using namespace std::literals;
// test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
// auto const alice = Account("alice");
// auto const bob = Account("bob");
// auto const broker = Account("broker");
// auto const gw = Account("gw");
// IOU const USD(gw["USD"]);
// env.fund(XRP(1000), gw, alice, bob, broker);
// env.close();
// env.trust(USD(100000), alice, bob);
// env.close();
// env(pay(gw, alice, USD(10000)));
// env(pay(gw, bob, USD(10000)));
// env.close();
// AccountID const zeroAcct{AccountID{}};
// auto const _exp = env.now() + 1s;
// auto const expiration = _exp.time_since_epoch().count();
// uint256 const optionId{getOptionIndex(zeroAcct, expiration)};
// env(optionlist(alice, expiration, XRP(10), XRP(10)), ter(tesSUCCESS));
// env.close();
// env(optioncreate(alice, optionId, 100, XRP(70)),
// txflags(tfAction),
// ter(tesSUCCESS));
// env(optioncreate(alice, optionId, 100, XRP(80)),
// txflags(tfAction),
// ter(tesSUCCESS));
// env(optioncreate(alice, optionId, 100, XRP(90)),
// txflags(tfAction),
// ter(tesSUCCESS));
// env(optioncreate(alice, optionId, 100, XRP(100)),
// txflags(tfAction),
// ter(tesSUCCESS));
// env(optioncreate(alice, optionId, 100, XRP(110)),
// txflags(tfAction),
// ter(tesSUCCESS));
// env.close();
// STAmount strikePrice = XRP(10);
// const uint256 uBookBaseOpp = getOptionBookBase(
// strikePrice.getIssuer(),
// strikePrice.getCurrency(),
// strikePrice.mantissa(),
// expiration);
// std::cout << "BOOK BASE: " << uBookBaseOpp << "\n";
// const uint256 uBookEndOpp = getOptionQualityNext(uBookBaseOpp);
// std::cout << "BOOK BASE END: " << uBookEndOpp << "\n";
// auto view = env.closed();
// auto key = view->succ(uBookBaseOpp, uBookEndOpp);
// if (key)
// {
// auto sleOfferDir = view->read(keylet::page(key.value()));
// uint256 offerIndex;
// unsigned int bookEntry;
// cdirFirst(
// *view, sleOfferDir->key(), sleOfferDir, bookEntry, offerIndex);
// auto sleOffer = view->read(keylet::unchecked(offerIndex));
// STAmount premium = sleOffer->getFieldAmount(sfAmount);
// auto const dir = to_string(sleOffer->getFieldH256(sfBookDirectory));
// std::cout << "dir: " << dir << "\n";
// auto const uTipIndex = sleOfferDir->key();
// auto const optionQuality = getOptionQuality(uTipIndex);
// std::cout << "optionQuality: " << optionQuality << "\n";
// STAmount dirRate = STAmount(premium.issue(), optionQuality);
// std::cout << "dirRate: " << dirRate << "\n";
// // BEAST_EXPECT(100 / rate == 110);
// }
// }
// void
// testEnabled(FeatureBitset features)
// {
// using namespace test::jtx;
// using namespace std::literals;
// testcase("enabled");
// test::jtx::Env env{*this, network::makeNetworkConfig(21337), features};
// // test::jtx::Env env{
// // *this,
// // network::makeNetworkConfig(21337),
// // features,
// // nullptr,
// // beast::severities::kTrace};
// auto const feeDrops = env.current()->fees().base;
// auto const writer = Account("alice");
// auto const buyer = Account("bob");
// auto const gw = Account("gateway");
// auto const USD = gw["USD"];
// env.fund(XRP(100000), writer, buyer, gw);
// env.close();
// BEAST_EXPECT(0 == 0);
// AccountID const zeroAcct{AccountID{}};
// auto const _exp = env.now() + 1s;
// auto const expiration = _exp.time_since_epoch().count();
// uint256 const optionId{getOptionIndex(zeroAcct, expiration)};
// auto preWriter = env.balance(writer);
// auto preBuyer = env.balance(buyer);
// auto const strikePrice = 10;
// env(optionlist(writer, expiration, XRP(strikePrice), XRP(strikePrice)),
// ter(tesSUCCESS));
// env.close();
// BEAST_EXPECT(env.balance(writer) == preWriter - feeDrops);
// // Call - Sell - Open
// auto const premium = 1;
// auto const quantity = 100;
// auto const seq = env.seq(writer);
// env(optioncreate(writer, optionId, quantity, XRP(premium)),
// txflags(tfAction),
// ter(tesSUCCESS));
// env.close();
// BEAST_EXPECT(
// env.balance(writer) ==
// preWriter - (feeDrops * 2) - XRP(quantity));
// BEAST_EXPECT(
// lockedValue(env, writer, seq) == XRP(quantity));
// auto jrr = getOptionBookOffers(env, XRP(strikePrice), expiration);
// std::cout << "RESULT: " << jrr << "\n";
// preWriter = env.balance(writer);
// preBuyer = env.balance(buyer);
// // Call - Buy - Open
// auto const seq1 = env.seq(buyer);
// env(optioncreate(buyer, optionId, quantity, XRP(premium)),
// ter(tesSUCCESS));
// env.close();
// uint256 const offerId{getOfferIndex(buyer, seq1)};
// BEAST_EXPECT(
// env.balance(buyer) ==
// preBuyer - feeDrops - XRP(quantity * premium));
// BEAST_EXPECT(
// env.balance(writer) ==
// preWriter + XRP(quantity * premium));
// preWriter = env.balance(writer);
// preBuyer = env.balance(buyer);
// auto jrr1 = getOptionBookOffers(env, XRP(strikePrice), expiration);
// std::cout << "RESULT1: " << jrr1 << "\n";
// // Execute Option
// env(optionexecute(buyer, optionId, offerId), ter(tesSUCCESS));
// env.close();
// BEAST_EXPECT(
// env.balance(buyer) ==
// preBuyer - feeDrops - XRP(quantity * strikePrice) + XRP(quantity));
// BEAST_EXPECT(
// env.balance(writer) ==
// preWriter + XRP(quantity * strikePrice));
// auto jrrList = getOptionList(env, zeroAcct);
// std::cout << "RESULT LIST: " << jrrList << "\n";
// auto jrr2 = getOptionBookOffers(env, XRP(strikePrice), expiration);
// std::cout << "RESULT2: " << jrr2 << "\n";
// }
void
testIC(FeatureBitset features)
{
using namespace test::jtx;
using namespace std::literals;
testcase("ic");
// test::jtx::Env env{*this, network::makeNetworkConfig(21337), features};
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337),
features,
nullptr,
beast::severities::kTrace};
auto const feeDrops = env.current()->fees().base;
auto const writer = Account("alice");
auto const buyer = Account("bob");
auto const gw = Account("gateway");
auto const GME = gw["GME"];
auto const USD = gw["USD"];
env.fund(XRP(100000), writer, buyer, gw);
env.close();
env.trust(USD(100000), writer, buyer);
env.close();
env(pay(gw, writer, USD(10000)));
env(pay(gw, buyer, USD(10000)));
env.close();
env.trust(GME(100000), writer, buyer);
env.close();
env(pay(gw, writer, USD(10000)));
env.close();
auto const _exp = env.now() + 1s;
auto const expiration = _exp.time_since_epoch().count();
uint256 const optionId{getOptionIndex(gw.id(), USD.currency, 20, expiration)};
auto preWriter = env.balance(writer);
auto preWriterGME = env.balance(writer, GME.issue());
auto preBuyer = env.balance(buyer);
auto preBuyerGME = env.balance(buyer, GME.issue());
auto const strikePrice = 20;
env(optionlist(writer, expiration, USD(strikePrice), GME(0)),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(writer) == preWriter - feeDrops);
auto const strikePrice1 = 25;
env(optionlist(writer, expiration, USD(strikePrice1), GME(0)),
ter(tesSUCCESS));
env.close();
// Call - Sell - Open
auto const premium = 0.5;
auto const quantity = 100;
auto const seq = env.seq(writer);
env(optioncreate(writer, optionId, quantity, XRP(premium)),
txflags(tfAction),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(
env.balance(writer) ==
preWriter - (feeDrops * 2));
BEAST_EXPECT(
env.balance(writer, GME.issue()) == preWriterGME - GME(quantity));
BEAST_EXPECT(
lockedValue(env, writer, seq) == GME(quantity));
auto jrr = getOptionBookOffers(env, GME(strikePrice), expiration);
std::cout << "RESULT: " << jrr << "\n";
preWriter = env.balance(writer);
preWriterGME = env.balance(writer, GME.issue());
preBuyer = env.balance(buyer);
preBuyerGME = env.balance(buyer, GME.issue());
// // Call - Buy - Open
// auto const seq1 = env.seq(buyer);
// env(optioncreate(buyer, optionId, quantity, GME(premium)),
// ter(tesSUCCESS));
// env.close();
// uint256 const offerId{getOfferIndex(buyer, seq1)};
// BEAST_EXPECT(
// env.balance(buyer) ==
// preBuyer - feeDrops - GME(quantity * premium));
// BEAST_EXPECT(
// env.balance(writer) ==
// preWriter + GME(quantity * premium));
// preWriter = env.balance(writer);
// preBuyer = env.balance(buyer);
// auto jrr1 = getOptionBookOffers(env, GME(strikePrice), expiration);
// std::cout << "RESULT1: " << jrr1 << "\n";
// // Execute Option
// env(optionexecute(buyer, optionId, offerId), ter(tesSUCCESS));
// env.close();
// BEAST_EXPECT(
// env.balance(buyer) ==
// preBuyer - feeDrops - GME(quantity * strikePrice) + GME(quantity));
// BEAST_EXPECT(
// env.balance(writer) ==
// preWriter + GME(quantity * strikePrice));
// auto jrrList = getOptionList(env, zeroAcct);
// std::cout << "RESULT LIST: " << jrrList << "\n";
// auto jrr2 = getOptionBookOffers(env, GME(strikePrice), expiration);
// std::cout << "RESULT2: " << jrr2 << "\n";
}
public:
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
// testBookBuy(sa);
// testBookSell(sa);
// testOptionBookBuy(sa);
// testEnabled(sa);
testIC(sa);
}
};
BEAST_DEFINE_TESTSUITE(Option, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/jss.h>
#include <test/jtx/escrow.h>

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/jss.h>
#include <test/jtx/paychan.h>

View File

@@ -19,6 +19,7 @@
#include <ripple/app/misc/AmendmentTable.h>
#include <ripple/beast/unit_test.h>
#include <ripple/json/json_reader.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>