This commit is contained in:
Denis Angell
2024-06-12 11:11:36 +02:00
parent f4f3fc0231
commit dddc7a6f72
10 changed files with 552 additions and 310 deletions

View File

@@ -178,16 +178,26 @@ OptionCreate::doApply()
STAmount const strikePrice = sleOptionAcc->getFieldAmount(sfStrikePrice);
std::uint32_t const expiration = sleOptionAcc->getFieldU32(sfExpiration);
STAmount const quantityShares = STAmount(strikePrice.issue(), isXRP(premium) ? quantity * 1000000 : quantity);
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.expiration: " << expiration << "\n";
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(),
@@ -226,11 +236,11 @@ OptionCreate::doApply()
{
JLOG(j.warn()) << "Creating Option Offer: !isClose";
// Add Option to Issuer
if (!isXRP(premium))
{
// unimplemented
return tecINTERNAL;
}
// if (!isXRP(premium))
// {
// // unimplemented
// return tecINTERNAL;
// }
// Add Option to Self
std::uint32_t ownerCount{(*sleSrcAcc)[sfOwnerCount]};
@@ -370,7 +380,7 @@ OptionCreate::doApply()
if (mSourceBalance < totalPremium.xrp())
return tecUNFUNDED_PAYMENT;
// subtract the balance from the buyer
// subtract the premium from the buyer
{
STAmount bal = mSourceBalance;
bal -= totalPremium.xrp();
@@ -379,7 +389,7 @@ OptionCreate::doApply()
sleSrcAcc->setFieldAmount(sfBalance, bal);
}
// add the balance to the writer
// add the premium to the writer
{
STAmount bal = sleOppAcc->getFieldAmount(sfBalance);
STAmount prior = bal;
@@ -389,6 +399,32 @@ OptionCreate::doApply()
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)};
// if (availableBuyerFunds < totalPremium)
// return tecUNFUNDED_PAYMENT;
// if (TER result = accountSend(
// sb, srcAccID, oppAccID, totalPremium, j, true);
// !isTesSuccess(result))
// return result;
return tecINTERNAL;
}
sb.update(sleOppAcc);
}
@@ -397,7 +433,7 @@ OptionCreate::doApply()
JLOG(j.warn()) << "Updating Option Balances: isSell";
if (isXRP(quantityShares))
{
// subtract the balance from the writer
// subtract the quantity from the writer
if (mSourceBalance < quantityShares.xrp())
return tecUNFUNDED_PAYMENT;
{
@@ -409,6 +445,19 @@ OptionCreate::doApply()
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 = sb.peek(keylet::line(srcAccID, quantityShares.getIssuer(), quantityShares.getCurrency()));
if (TER const result = trustAdjustLockedBalance(ctx_.view(), sleLine, quantityShares, 1, ctx_.journal, true); !isTesSuccess(result))
return result;
}
}
// apply

View File

@@ -98,6 +98,9 @@ OptionExecute::doApply()
std::uint32_t const quantity = sleOptionOffer->getFieldU32(sfQuantity);
STAmount const totalValue = STAmount(strikePrice.issue(), (strikePrice.mantissa() * quantity));
JLOG(j.warn()) << "OptionExecute: QUANTITY SHARES" << quantityShares << "\n";
JLOG(j.warn()) << "OptionExecute: TOTAL VALUE" << totalValue << "\n";
if (flags & tfOptionExpire)
{
JLOG(j.warn()) << "OptionExecute: EXPIRE OPTION";
@@ -111,39 +114,95 @@ OptionExecute::doApply()
{
case 0: {
JLOG(j.warn()) << "OptionExecute: EXERCISE CALL";
if (isXRP(quantityShares))
{
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;
STAmount hBalance = mSourceBalance;
// 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(
sb,
std::vector<AccountID>{srcAccID, oppAccID},
totalValue.issue(),
j);
if (!isTesSuccess(canBuyerXfer))
{
return canBuyerXfer;
}
STAmount availableBuyerFunds{accountFunds(
sb, srcAccID, totalValue, fhZERO_IF_FROZEN, j)};
// subtract the total value from the buyer
{
hBalance -= totalValue.xrp();
if (hBalance < beast::zero || hBalance > mSourceBalance)
return tecINTERNAL;
}
if (availableBuyerFunds < totalValue)
return tecUNFUNDED_PAYMENT;
// 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);
}
if (TER result = accountSend(
sb, srcAccID, oppAccID, totalValue, j, true);
!isTesSuccess(result))
return result;
}
// add the shares to the buyer
{
STAmount prior = hBalance;
hBalance += quantityShares.xrp();
if (hBalance < beast::zero || hBalance < prior)
return tecINTERNAL;
}
// 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
{
AccountID const issuerAccID = totalValue.getIssuer();
{
// check permissions
if (issuerAccID == oppAccID || issuerAccID == srcAccID)
{
// no permission check needed when the issuer sends out or a
// subscriber sends back RH TODO: move this condition into
// trustTransferAllowed, guarded by an amendment
}
else if (TER canXfer = trustTransferAllowed(
sb,
std::vector<AccountID>{oppAccID, srcAccID},
quantityShares.issue(),
j);
!isTesSuccess(canXfer))
return canXfer;
STAmount availableFunds{accountFunds(sb, oppAccID, quantityShares, fhZERO_IF_FROZEN, j)};
if (availableFunds < quantityShares)
return tecUNFUNDED_PAYMENT;
// action the transfer
if (TER result = accountSend(sb, oppAccID, srcAccID, quantityShares, j, true); !isTesSuccess(result))
return result;
}
}
break;
}
case 1: {

View File

@@ -63,12 +63,14 @@ OptionList::doApply()
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(), expiration);
std::optional<Keylet> const optionKeylet = keylet::option(strikePrice.getIssuer(), strikePrice.getCurrency(), strikePrice.mantissa(), expiration);
if (sb.exists(*optionKeylet))
return tecDUPLICATE;
@@ -88,6 +90,8 @@ OptionList::doApply()
(*sleOption)[sfOwnerNode] = *newPage;
(*sleOption)[sfStrikePrice] = strikePrice;
(*sleOption)[sfIssuer] = issuer;
(*sleOption)[sfCurrency] = currency;
(*sleOption)[sfExpiration] = expiration;
// apply

View File

@@ -298,7 +298,7 @@ Keylet
uritoken(AccountID const& issuer, Blob const& uri);
Keylet
option(AccountID const& issuer, std::uint32_t expiration) noexcept;
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;

View File

@@ -444,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;

View File

@@ -474,9 +474,9 @@ uritoken(AccountID const& issuer, Blob const& uri)
}
Keylet
option(AccountID const& issuer, std::uint32_t expiration) noexcept
option(AccountID const& issuer, Currency const& currency, std::uint64_t strike, std::uint32_t expiration) noexcept
{
return {ltOPTION,indexHash(LedgerNameSpace::OPTION, issuer, expiration)};
return {ltOPTION, indexHash(LedgerNameSpace::OPTION, issuer, currency, strike, expiration)};
}
Keylet

View File

@@ -370,6 +370,8 @@ LedgerFormats::LedgerFormats()
{
{sfOwnerNode, soeREQUIRED},
{sfStrikePrice, soeREQUIRED},
{sfIssuer, soeREQUIRED},
{sfCurrency, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}

View File

@@ -197,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);

View File

@@ -461,6 +461,8 @@ TxFormats::TxFormats()
ttOPTION_LIST,
{
{sfStrikePrice, soeREQUIRED},
{sfIssuer, soeREQUIRED},
{sfCurrency, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfTicketSequence, soeOPTIONAL},
},

View File

@@ -67,13 +67,16 @@ struct Option_test : public beast::unit_test::suite
optionlist(
jtx::Account const& account,
std::uint32_t expiration,
STAmount const& strikePrice)
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;
}
@@ -114,9 +117,11 @@ struct Option_test : public beast::unit_test::suite
static uint256
getOptionIndex(
AccountID const& issuer,
Currency const& currency,
std::uint64_t const& strike,
std::uint32_t expiration)
{
return keylet::option(issuer, expiration).key;
return keylet::option(issuer, currency, strike, expiration).key;
}
static uint256
@@ -152,276 +157,390 @@ struct Option_test : public beast::unit_test::suite
return env.rpc("json", "option_book_offers", to_string(jvbp))[jss::result];
}
void
testBookBuy(FeatureBitset features)
{
testcase("book buy");
// void
// testBookBuy(FeatureBitset features)
// {
// testcase("book buy");
using namespace test::jtx;
using namespace std::literals;
// using namespace test::jtx;
// using namespace std::literals;
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
// 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"]);
// 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();
// 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();
// // 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
// // Bob offers to buy 110 USD for 100 XRP.
// // env(offer(bob, USD(110), XRP(100))); // <- Best
Book const book1{
xrpIssue(),
USD.issue(),
};
// 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);
}
}
// 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
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)
testIC(FeatureBitset features)
{
using namespace test::jtx;
using namespace std::literals;
testcase("enabled");
testcase("ic");
test::jtx::Env env{*this, network::makeNetworkConfig(21337), features};
// test::jtx::Env env{
// *this,
// network::makeNetworkConfig(21337),
// features,
// nullptr,
// beast::severities::kTrace};
// 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();
BEAST_EXPECT(0 == 0);
AccountID const zeroAcct{AccountID{}};
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(zeroAcct, expiration)};
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 = 10;
env(optionlist(writer, expiration, XRP(strikePrice)),
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 = 1;
auto const premium = 0.5;
auto const quantity = 100;
auto const seq = env.seq(writer);
env(optioncreate(writer, optionId, quantity, XRP(premium)),
@@ -431,51 +550,55 @@ struct Option_test : public beast::unit_test::suite
BEAST_EXPECT(
env.balance(writer) ==
preWriter - (feeDrops * 2) - XRP(quantity));
preWriter - (feeDrops * 2));
BEAST_EXPECT(
lockedValue(env, writer, seq) == XRP(quantity));
env.balance(writer, GME.issue()) == preWriterGME - GME(quantity));
BEAST_EXPECT(
lockedValue(env, writer, seq) == GME(quantity));
auto jrr = getOptionBookOffers(env, XRP(strikePrice), expiration);
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, XRP(premium)),
ter(tesSUCCESS));
env.close();
// // 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 - XRP(quantity * premium));
BEAST_EXPECT(
env.balance(writer) ==
preWriter + XRP(quantity * premium));
// 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);
// preWriter = env.balance(writer);
// preBuyer = env.balance(buyer);
auto jrr1 = getOptionBookOffers(env, XRP(strikePrice), expiration);
std::cout << "RESULT1: " << jrr1 << "\n";
// auto jrr1 = getOptionBookOffers(env, GME(strikePrice), expiration);
// std::cout << "RESULT1: " << jrr1 << "\n";
// Execute Option
env(optionexecute(buyer, optionId, offerId), ter(tesSUCCESS));
env.close();
// // 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));
// 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, XRP(strikePrice), expiration);
std::cout << "RESULT2: " << jrr2 << "\n";
// auto jrrList = getOptionList(env, zeroAcct);
// std::cout << "RESULT LIST: " << jrrList << "\n";
// auto jrr2 = getOptionBookOffers(env, GME(strikePrice), expiration);
// std::cout << "RESULT2: " << jrr2 << "\n";
}
public:
@@ -487,7 +610,8 @@ public:
// testBookBuy(sa);
// testBookSell(sa);
// testOptionBookBuy(sa);
testEnabled(sa);
// testEnabled(sa);
testIC(sa);
}
};