options build

This commit is contained in:
Denis Angell
2024-02-16 22:35:32 +01:00
parent acd455f5df
commit 625ca21d2f
41 changed files with 1795 additions and 113 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/OptionExecute.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
@@ -732,6 +737,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

@@ -109,6 +109,10 @@ XRPNotCreated::visitEntry(
if (isXRP((*before)[sfAmount]))
drops_ -= (*before)[sfAmount].xrp().drops();
break;
case ltOPTION_OFFER:
if (isXRP((*before)[sfAmount]))
drops_ -= (*before)[sfAmount].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)[sfAmount]))
drops_ += (*after)[sfAmount].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,422 @@
//------------------------------------------------------------------------------
/*
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[~sfInvoiceID];
std::uint32_t const quantity = ctx.tx.getFieldU32(sfQualityIn);
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(sfQualityOut) == 0)
{
continue;
}
// looking for oposite option position.
if (_isPut == isPut && _isSell != isSell)
{
uint256 const sealID = sleItem->getFieldH256(sfInvoiceID);
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(sfOfferID);
auto const flags = ctx_.tx.getFlags();
STAmount const premium = ctx_.tx.getFieldAmount(sfAmount);
std::uint32_t const quantity = ctx_.tx.getFieldU32(sfQualityIn);
std::optional<uint256> const swapID = ctx_.tx[~sfInvoiceID];
STAmount const totalPremium =
STAmount(premium.issue(), (premium.mantissa() * quantity));
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(sfAmount);
std::uint32_t const expiration = sleOptionAcc->getFieldU32(sfExpiration);
STAmount const quantityShares = STAmount(strikePrice.issue(), isXRP(premium) ? quantity * 1000000 : quantity);
bool const isPut = (flags & tfType) != 0;
bool const isSell = (flags & tfAction) != 0;
bool const isClose = (flags & tfPosition) != 0;
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(sfInvoiceID, optionOfferKeylet.key);
uint32_t currSealed = sealedOption->getFieldU32(sfQualityOut);
sealedOption->setFieldU32(sfQualityOut, 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(sfOfferID, optionID);
optionOffer->setFieldU32(sfQualityIn, quantity);
optionOffer->setFieldU32(sfQualityOut, quantity);
optionOffer->setFieldAmount(sfTakerPays, STAmount(0)); // Premium
optionOffer->setFieldAmount(sfAmount, STAmount(0)); // Locked
if (sealID)
{
JLOG(j.warn()) << "Updating Option Offer: sealID";
optionOffer->setFieldH256(sfInvoiceID, *sealID);
optionOffer->setFieldU32(sfQualityOut, 0);
}
if (isSell)
{
JLOG(j.warn()) << "Updating Option Offer: isSell";
// Update the locked balance and premium
optionOffer->setFieldAmount(sfTakerPays, premium); // Premium
optionOffer->setFieldAmount(sfAmount, 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(sfInvoiceID);
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(sfInvoiceID, optionOfferKeylet.key);
sb.update(swapOption);
// Update New Option
optionOffer->setFieldH256(sfInvoiceID, *swapID);
sb.update(optionOffer);
// Erase Swap Sealed Option
uint256 const swapSealedID = swapOption->getFieldH256(sfInvoiceID);
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 balance from the buyer
{
STAmount bal = mSourceBalance;
bal -= totalPremium.xrp();
if (bal < beast::zero || bal > mSourceBalance)
return tecINTERNAL;
sleSrcAcc->setFieldAmount(sfBalance, bal);
}
// add the balance 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);
}
}
sb.update(sleOppAcc);
}
else
{
JLOG(j.warn()) << "Updating Option Balances: isSell";
if (isXRP(quantityShares))
{
// subtract the balance 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);
}
}
}
// 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,203 @@
//------------------------------------------------------------------------------
/*
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/OptionExecute.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
OptionExecute::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
OptionExecute::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
OptionExecute::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(sfOfferID);
uint256 const offerID = ctx_.tx.getFieldH256(sfInvoiceID);
auto const flags = ctx_.tx.getFlags();
auto sleSrcAcc = sb.peek(keylet::account(srcAccID));
if (!sleSrcAcc)
return terNO_ACCOUNT;
auto optionOfferKeylet = ripple::keylet::unchecked(offerID);
auto sleOptionOffer = sb.peek(optionOfferKeylet);
if (!sleOptionOffer)
return tecNO_TARGET;
AccountID const ownrAccID = sleOptionOffer->getAccountID(sfOwner);
auto const sealedID = sleOptionOffer->getFieldH256(sfInvoiceID);
auto oppOfferKeylet = ripple::keylet::unchecked(sealedID);
auto sleSealedOffer = sb.peek(oppOfferKeylet);
if (!sleSealedOffer)
return tecNO_TARGET;
AccountID const oppAccID = sleSealedOffer->getAccountID(sfOwner);
auto sleOppAcc = sb.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 = sb.peek(keylet::unchecked(optionID));
if (!sleOption)
return tecINTERNAL;
STAmount const strikePrice = sleOption->getFieldAmount(sfAmount);
STAmount const quantityShares = sleSealedOffer->getFieldAmount(sfAmount);
std::uint32_t const quantity = sleOptionOffer->getFieldU32(sfQualityIn);
STAmount const totalValue = STAmount(strikePrice.issue(), (strikePrice.mantissa() * quantity));
if (flags & tfOptionExpire)
{
JLOG(j.warn()) << "OptionExecute: EXPIRE OPTION";
sb.erase(sleSealedOffer);
sb.erase(sleOptionOffer);
sb.apply(ctx_.rawView());
return tesSUCCESS;
}
switch (isPut)
{
case 0: {
JLOG(j.warn()) << "OptionExecute: EXERCISE CALL";
if (isXRP(quantityShares))
{
if (mSourceBalance < totalValue.xrp())
return tecUNFUNDED_PAYMENT;
STAmount hBalance = mSourceBalance;
// subtract the total value from the buyer
{
hBalance -= totalValue.xrp();
if (hBalance < beast::zero || hBalance > mSourceBalance)
return tecINTERNAL;
}
// add the total value to the writer
{
STAmount wBalance = sleOppAcc->getFieldAmount(sfBalance);
STAmount prior = wBalance;
wBalance += totalValue.xrp();
if (wBalance < beast::zero || wBalance < prior)
return tecINTERNAL;
sleOppAcc->setFieldAmount(sfBalance, wBalance);
}
// add the shares to the buyer
{
STAmount prior = hBalance;
hBalance += quantityShares.xrp();
if (hBalance < beast::zero || hBalance < prior)
return tecINTERNAL;
}
sleSrcAcc->setFieldAmount(sfBalance, hBalance);
}
break;
}
case 1: {
JLOG(j.warn()) << "OptionExecute: 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
sb.update(sleOppAcc);
sb.update(sleSrcAcc);
sb.erase(sleSealedOffer);
sb.erase(sleOptionOffer);
sb.apply(ctx_.rawView());
return tesSUCCESS;
}
XRPAmount
OptionExecute::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 OptionExecute : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit OptionExecute(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,88 @@
//------------------------------------------------------------------------------
/*
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 amount = ctx_.tx.getFieldAmount(sfAmount);
auto sleSrcAcc = sb.peek(keylet::account(srcAccID));
if (!sleSrcAcc)
return terNO_ACCOUNT;
auto const optionKeylet = keylet::option(amount.getIssuer(), expiration);
auto const sleOption = std::make_shared<SLE>(optionKeylet);
(*sleOption)[sfAmount] = amount;
(*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/OptionExecute.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_EXECUTE:
return invoke_preflight_helper<OptionExecute>(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_EXECUTE:
return invoke_preclaim<OptionExecute>(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_EXECUTE:
return OptionExecute::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_EXECUTE: {
OptionExecute 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,27 @@ import_vlseq(PublicKey const& key) noexcept;
Keylet
uritoken(AccountID const& issuer, Blob const& uri);
Keylet
broker() noexcept;;
Keylet
brokerStateDir(AccountID const& id, AccountID const& account) noexcept;
Keylet
brokerState(AccountID const& id, AccountID const& issuer, uint256 const& ns) noexcept;
Keylet
option(AccountID const& issuer, 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;
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 +332,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

@@ -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,
tfAction = 0x00020000,
tfPosition = 0x00040000,
};
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_EXECUTE = 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, std::uint32_t expiration) noexcept
{
return {ltOPTION,indexHash(LedgerNameSpace::OPTION, issuer, 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,35 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::Option,
ltOPTION,
{
{sfAmount, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}
},
commonFields);
add(jss::OptionOffer,
ltOPTION_OFFER,
{
{sfOwner, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfOfferID, soeREQUIRED}, // OptionID
{sfAmount, soeREQUIRED}, // Locked Amount
{sfTakerPays, soeREQUIRED}, // Premium
{sfQualityIn, soeREQUIRED}, // Quantity
{sfQualityOut, soeREQUIRED}, // To Seal
{sfBookDirectory, soeREQUIRED},
{sfBookNode, soeREQUIRED},
{sfInvoiceID, 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

@@ -456,6 +456,35 @@ TxFormats::TxFormats()
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::OptionList,
ttOPTION_LIST,
{
{sfAmount, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::OptionCreate,
ttOPTION_CREATE,
{
{sfOfferID, soeREQUIRED}, // Option ID
{sfAmount, soeREQUIRED}, // Premium
{sfQualityIn, soeREQUIRED}, // Quantity
{sfInvoiceID, soeOPTIONAL}, // Swap ID
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::OptionExecute,
ttOPTION_EXECUTE,
{
{sfOfferID, soeREQUIRED},
{sfInvoiceID, 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(OptionExecute); // 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.

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

@@ -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,459 @@
//------------------------------------------------------------------------------
/*
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(sfAmount))
return (*sle)[sfAmount];
return STAmount(0);
}
Json::Value
optionlist(
jtx::Account const& account,
std::uint32_t expiration,
STAmount const& amount)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::OptionList;
jv[jss::Account] = account.human();
jv[jss::Amount] = amount.getJson(JsonOptions::none);
jv[sfExpiration.jsonName] = expiration;
return jv;
}
Json::Value
optioncreate(
jtx::Account const& account,
uint256 const& optionId,
uint32_t const& quantity,
STAmount const& amount)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::OptionCreate;
jv[jss::Account] = account.human();
jv[sfOfferID.jsonName] = to_string(optionId);
jv[jss::Amount] = amount.getJson(JsonOptions::none);
jv[sfQualityIn.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::OptionExecute;
jv[jss::Account] = account.human();
jv[sfOfferID.jsonName] = to_string(optionId);
jv[sfInvoiceID.jsonName] = to_string(offerId);
;
return jv;
}
static uint256
getOptionIndex(
AccountID const& issuer,
std::uint32_t expiration)
{
return keylet::option(issuer, expiration).key;
}
static uint256
getOfferIndex(AccountID const& account, std::uint32_t sequence)
{
return keylet::optionOffer(account, sequence).key;
}
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)), 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::kWarning};
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)),
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));
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);
// 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));
}
public:
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
// testBookBuy(sa);
// testBookSell(sa);
// testOptionBookBuy(sa);
testEnabled(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>