diff --git a/src/ripple/app/tx/impl/OptionCreate.cpp b/src/ripple/app/tx/impl/OptionCreate.cpp index 47f759829..4a81007bb 100644 --- a/src/ripple/app/tx/impl/OptionCreate.cpp +++ b/src/ripple/app/tx/impl/OptionCreate.cpp @@ -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{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 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 diff --git a/src/ripple/app/tx/impl/OptionExecute.cpp b/src/ripple/app/tx/impl/OptionExecute.cpp index 6ac438de0..2176b24ff 100644 --- a/src/ripple/app/tx/impl/OptionExecute.cpp +++ b/src/ripple/app/tx/impl/OptionExecute.cpp @@ -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{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{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: { diff --git a/src/ripple/app/tx/impl/OptionList.cpp b/src/ripple/app/tx/impl/OptionList.cpp index 68b29f35a..a988c98d6 100644 --- a/src/ripple/app/tx/impl/OptionList.cpp +++ b/src/ripple/app/tx/impl/OptionList.cpp @@ -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 const optionKeylet = keylet::option(strikePrice.getIssuer(), expiration); + std::optional 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 diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index e75c2c5bb..fabde34c7 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -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; diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index d0e22eaab..3104aba90 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -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; diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index f3fa85d59..a020c5872 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -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 diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index c90c0bf64..e26078b5a 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -370,6 +370,8 @@ LedgerFormats::LedgerFormats() { {sfOwnerNode, soeREQUIRED}, {sfStrikePrice, soeREQUIRED}, + {sfIssuer, soeREQUIRED}, + {sfCurrency, soeREQUIRED}, {sfExpiration, soeREQUIRED}, {sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED} diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index bc4fc9540..7749f5be0 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -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); diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 5e552c9a5..15a14c6a8 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -461,6 +461,8 @@ TxFormats::TxFormats() ttOPTION_LIST, { {sfStrikePrice, soeREQUIRED}, + {sfIssuer, soeREQUIRED}, + {sfCurrency, soeREQUIRED}, {sfExpiration, soeREQUIRED}, {sfTicketSequence, soeOPTIONAL}, }, diff --git a/src/test/app/Option_test.cpp b/src/test/app/Option_test.cpp index 6d05e5a8e..ea682e13d 100644 --- a/src/test/app/Option_test.cpp +++ b/src/test/app/Option_test.cpp @@ -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); } };