From 28ae522ea2d85d066ceeb23d30ade451fd1e424d Mon Sep 17 00:00:00 2001 From: Mike Ellery Date: Fri, 9 Dec 2016 13:58:50 -0800 Subject: [PATCH] Migrate freeze-test to cpp (RIPD-1154): Port all active tests in freeze-test.coffee to c++. --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/test/app/Freeze_test.cpp | 535 ++++++++++++++++++ src/unity/app_test_unity.cpp | 1 + 4 files changed, 543 insertions(+) create mode 100644 src/test/app/Freeze_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 47bb10603..9015de5d8 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4364,6 +4364,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index e872feaeb..e8ff774f5 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5151,6 +5151,9 @@ test\app + + test\app + test\app diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp new file mode 100644 index 000000000..b80ad2852 --- /dev/null +++ b/src/test/app/Freeze_test.cpp @@ -0,0 +1,535 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-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 +#include +#include +#include +#include + +namespace ripple { + +class Freeze_test : public beast::unit_test::suite +{ + + static Json::Value + getAccountLines(test::jtx::Env& env, test::jtx::Account const& account) + { + Json::Value jq; + jq[jss::account] = account.human(); + return env.rpc("json", "account_lines", to_string(jq))[jss::result]; + } + + static Json::Value + getAccountOffers( + test::jtx::Env& env, + test::jtx::Account const& account, + bool current = false) + { + Json::Value jq; + jq[jss::account] = account.human(); + jq[jss::ledger_index] = current ? "current" : "validated"; + return env.rpc("json", "account_offers", to_string(jq))[jss::result]; + } + + static bool checkArraySize(Json::Value const& val, unsigned int size) + { + return val.isArray() && val.size() == size; + } + + void testRippleState() + { + testcase("RippleState Freeze"); + + using namespace test::jtx; + Env env(*this); + + Account G1 {"G1"}; + Account alice {"alice"}; + Account bob {"bob"}; + + env.fund(XRP(1000), G1, alice, bob); + env.close(); + + env.trust(G1["USD"](100), bob); + env.trust(G1["USD"](100), alice); + env.close(); + + env(pay(G1, bob, G1["USD"](10))); + env(pay(G1, alice, G1["USD"](100))); + env.close(); + + env(offer(alice, XRP(500), G1["USD"](100))); + env.close(); + + { + auto lines = getAccountLines(env, bob); + if(! BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u))) + return; + BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human()); + BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100"); + BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10"); + } + + { + auto lines = getAccountLines(env, alice); + if(! BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u))) + return; + BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human()); + BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100"); + BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100"); + } + + { + // Account with line unfrozen (proving operations normally work) + // test: can make Payment on that line + env(pay(alice, bob, G1["USD"](1))); + + // test: can receive Payment on that line + env(pay(bob, alice, G1["USD"](1))); + env.close(); + } + + { + // Is created via a TrustSet with SetFreeze flag + // test: sets LowFreeze | HighFreeze flags + env(trust(G1, bob["USD"](0), tfSetFreeze)); + auto affected = env.meta()->getJson(0)[sfAffectedNodes.fieldName]; + if(! BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = + affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfLowLimit.fieldName] == G1["USD"](0).value().getJson(0)); + BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze); + BEAST_EXPECT(! (ff[jss::Flags].asUInt() & lsfHighFreeze)); + env.close(); + } + + { + // Account with line frozen by issuer + // test: can buy more assets on that line + env(offer(bob, G1["USD"](5), XRP(25))); + auto affected = env.meta()->getJson(0)[sfAffectedNodes.fieldName]; + if(! BEAST_EXPECT(checkArraySize(affected, 5u))) + return; + auto ff = + affected[3u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfHighLimit.fieldName] == bob["USD"](100).value().getJson(0)); + auto amt = + STAmount{Issue{to_currency("USD"), noAccount()}, -15} + .value().getJson(0); + BEAST_EXPECT(ff[sfBalance.fieldName] == amt); + env.close(); + } + + { + // test: can not sell assets from that line + env(offer(bob, XRP(1), G1["USD"](5)), + ter(tecUNFUNDED_OFFER)); + + // test: can receive Payment on that line + env(pay(alice, bob, G1["USD"](1))); + + // test: can not make Payment from that line + env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY)); + } + + { + // check G1 account lines + // test: shows freeze + auto lines = getAccountLines(env, G1); + Json::Value bobLine; + for (auto const& it : lines[jss::lines]) + { + if(it[jss::account] == bob.human()) + { + bobLine = it; + break; + } + } + if(! BEAST_EXPECT(bobLine)) + return; + BEAST_EXPECT(bobLine[jss::freeze] == true); + BEAST_EXPECT(bobLine[jss::balance] == "-16"); + } + + { + // test: shows freeze peer + auto lines = getAccountLines(env, bob); + Json::Value g1Line; + for (auto const& it : lines[jss::lines]) + { + if(it[jss::account] == G1.human()) + { + g1Line = it; + break; + } + } + if(! BEAST_EXPECT(g1Line)) + return; + BEAST_EXPECT(g1Line[jss::freeze_peer] == true); + BEAST_EXPECT(g1Line[jss::balance] == "16"); + } + + { + //Is cleared via a TrustSet with ClearFreeze flag + // test: sets LowFreeze | HighFreeze flags + env(trust(G1, bob["USD"](0), tfClearFreeze)); + auto affected = env.meta()->getJson(0)[sfAffectedNodes.fieldName]; + if(! BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = + affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfLowLimit.fieldName] == G1["USD"](0).value().getJson(0)); + BEAST_EXPECT(! (ff[jss::Flags].asUInt() & lsfLowFreeze)); + BEAST_EXPECT(! (ff[jss::Flags].asUInt() & lsfHighFreeze)); + env.close(); + } + } + + void + testGlobalFreeze() + { + testcase("Global Freeze"); + + using namespace test::jtx; + Env env(*this); + + Account G1 {"G1"}; + Account A1 {"A1"}; + Account A2 {"A2"}; + Account A3 {"A3"}; + Account A4 {"A4"}; + + env.fund(XRP(12000), G1); + env.fund(XRP(1000), A1); + env.fund(XRP(20000), A2, A3, A4); + env.close(); + + env.trust(G1["USD"](1200), A1); + env.trust(G1["USD"](200), A2); + env.trust(G1["BTC"](100), A3); + env.trust(G1["BTC"](100), A4); + env.close(); + + env(pay(G1, A1, G1["USD"](1000))); + env(pay(G1, A2, G1["USD"](100))); + env(pay(G1, A3, G1["BTC"](100))); + env(pay(G1, A4, G1["BTC"](100))); + env.close(); + + env(offer(G1, XRP(10000), G1["USD"](100)), txflags(tfPassive)); + env(offer(G1, G1["USD"](100), XRP(10000)), txflags(tfPassive)); + env(offer(A1, XRP(10000), G1["USD"](100)), txflags(tfPassive)); + env(offer(A2, G1["USD"](100), XRP(10000)), txflags(tfPassive)); + env.close(); + + { + //Is toggled via AccountSet using SetFlag and ClearFlag + // test: SetFlag GlobalFreeze + env.require(nflags(G1, asfGlobalFreeze)); + env(fset(G1, asfGlobalFreeze)); + env.require(flags(G1, asfGlobalFreeze)); + env.require(nflags(G1, asfNoFreeze)); + + // test: ClearFlag GlobalFreeze + env(fclear(G1, asfGlobalFreeze)); + env.require(nflags(G1, asfGlobalFreeze)); + env.require(nflags(G1, asfNoFreeze)); + } + + { + //Account without GlobalFreeze (proving operations normally work) + // test: visible offers where taker_pays is unfrozen issuer + auto offers = + env.rpc("book_offers", + std::string("USD/")+G1.human(), "XRP") + [jss::result][jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 2u))) + return; + std::set accounts; + for (auto const& offer : offers) + { + accounts.insert(offer[jss::Account].asString()); + } + BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts)); + BEAST_EXPECT(accounts.find(G1.human()) != std::end(accounts)); + + // test: visible offers where taker_gets is unfrozen issuer + offers = + env.rpc("book_offers", + "XRP", std::string("USD/")+G1.human()) + [jss::result][jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 2u))) + return; + accounts.clear(); + for (auto const& offer : offers) + { + accounts.insert(offer[jss::Account].asString()); + } + BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts)); + BEAST_EXPECT(accounts.find(G1.human()) != std::end(accounts)); + } + + { + // Offers/Payments + // test: assets can be bought on the market + env(offer(A3, G1["BTC"](1), XRP(1))); + + // test: assets can be sold on the market + env(offer(A4, XRP(1), G1["BTC"](1))); + + // test: direct issues can be sent + env(pay(G1, A2, G1["USD"](1))); + + // test: direct redemptions can be sent + env(pay(A2, G1, G1["USD"](1))); + + // test: via rippling can be sent + env(pay(A2, A1, G1["USD"](1))); + + // test: via rippling can be sent back + env(pay(A1, A2, G1["USD"](1))); + } + + { + // Account with GlobalFreeze + // set GlobalFreeze first + // test: SetFlag GlobalFreeze will toggle back to freeze + env.require(nflags(G1, asfGlobalFreeze)); + env(fset(G1, asfGlobalFreeze)); + env.require(flags(G1, asfGlobalFreeze)); + env.require(nflags(G1, asfNoFreeze)); + + // test: assets can't be bought on the market + env(offer(A3, G1["BTC"](1), XRP(1)), ter(tecFROZEN)); + + // test: assets can't be sold on the market + env(offer(A4, XRP(1), G1["BTC"](1)), ter(tecFROZEN)); + } + + { + // offers are filtered (seems to be broken?) + // test: account_offers always shows own offers + auto offers = getAccountOffers(env, G1)[jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 2u))) + return; + + // test: book_offers shows offers + // (should these actually be filtered?) + offers = + env.rpc("book_offers", + "XRP", std::string("USD/")+G1.human()) + [jss::result][jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 2u))) + return; + + offers = + env.rpc("book_offers", + std::string("USD/")+G1.human(), "XRP") + [jss::result][jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 2u))) + return; + } + + { + // Payments + // test: direct issues can be sent + env(pay(G1, A2, G1["USD"](1))); + + // test: direct redemptions can be sent + env(pay(A2, G1, G1["USD"](1))); + + // test: via rippling cant be sent + env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + } + } + + void + testNoFreeze() + { + testcase("No Freeze"); + + using namespace test::jtx; + Env env(*this); + + Account G1 {"G1"}; + Account A1 {"A1"}; + + env.fund(XRP(12000), G1); + env.fund(XRP(1000), A1); + env.close(); + + env.trust(G1["USD"](1000), A1); + env.close(); + + env(pay(G1, A1, G1["USD"](1000))); + env.close(); + + //TrustSet NoFreeze + // test: should set NoFreeze in Flags + env.require(nflags(G1, asfNoFreeze)); + env(fset(G1, asfNoFreeze)); + env.require(flags(G1, asfNoFreeze)); + env.require(nflags(G1, asfGlobalFreeze)); + + // test: cannot be cleared + env(fclear(G1, asfNoFreeze)); + env.require(flags(G1, asfNoFreeze)); + env.require(nflags(G1, asfGlobalFreeze)); + + // test: can set GlobalFreeze + env(fset(G1, asfGlobalFreeze)); + env.require(flags(G1, asfNoFreeze)); + env.require(flags(G1, asfGlobalFreeze)); + + // test: cannot unset GlobalFreeze + env(fclear(G1, asfGlobalFreeze)); + env.require(flags(G1, asfNoFreeze)); + env.require(flags(G1, asfGlobalFreeze)); + + // test: trustlines can't be frozen + env(trust(G1, A1["USD"](0), tfSetFreeze)); + auto affected = env.meta()->getJson(0)[sfAffectedNodes.fieldName]; + if(! BEAST_EXPECT(checkArraySize(affected, 1u))) + return; + + auto let = + affected[0u][sfModifiedNode.fieldName][sfLedgerEntryType.fieldName]; + BEAST_EXPECT(let == "AccountRoot"); + } + + void + testOffersWhenFrozen() + { + testcase("Offers for Frozen Trust Lines"); + + using namespace test::jtx; + Env env(*this); + + Account G1 {"G1"}; + Account A2 {"A2"}; + Account A3 {"A3"}; + Account A4 {"A4"}; + + env.fund(XRP(1000), G1, A3, A4); + env.fund(XRP(2000), A2); + env.close(); + + env.trust(G1["USD"](1000), A2); + env.trust(G1["USD"](2000), A3); + env.trust(G1["USD"](2000), A4); + env.close(); + + env(pay(G1, A3, G1["USD"](2000))); + env(pay(G1, A4, G1["USD"](2000))); + env.close(); + + env(offer(A3, XRP(1000), G1["USD"](1000)), txflags(tfPassive)); + env.close(); + + // removal after successful payment + // test: make a payment with partially consuming offer + env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1))); + env.close(); + + // test: offer was only partially consumed + auto offers = getAccountOffers(env, A3)[jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 1u))) + return; + BEAST_EXPECT( + offers[0u][jss::taker_gets] == G1["USD"](999).value().getJson(0)); + + // test: someone else creates an offer providing liquidity + env(offer(A4, XRP(999), G1["USD"](999))); + env.close(); + + // test: owner of partially consumed offers line is frozen + env(trust(G1, A3["USD"](0), tfSetFreeze)); + auto affected = env.meta()->getJson(0)[sfAffectedNodes.fieldName]; + if(! BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = + affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfHighLimit.fieldName] == G1["USD"](0).value().getJson(0)); + BEAST_EXPECT(! (ff[jss::Flags].asUInt() & lsfLowFreeze)); + BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfHighFreeze); + env.close(); + + // verify offer on the books + offers = getAccountOffers(env, A3)[jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 1u))) + return; + + // test: Can make a payment via the new offer + env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1))); + env.close(); + + // test: Partially consumed offer was removed by tes* payment + offers = getAccountOffers(env, A3)[jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 0u))) + return; + + // removal buy successful OfferCreate + // test: freeze the new offer + env(trust(G1, A4["USD"](0), tfSetFreeze)); + affected = env.meta()->getJson(0)[sfAffectedNodes.fieldName]; + if(! BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + ff = + affected[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfLowLimit.fieldName] == G1["USD"](0).value().getJson(0)); + BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze); + BEAST_EXPECT(! (ff[jss::Flags].asUInt() & lsfHighFreeze)); + env.close(); + + // test: can no longer create a crossing offer + env(offer(A2, G1["USD"](999), XRP(999))); + affected = env.meta()->getJson(0)[sfAffectedNodes.fieldName]; + if(! BEAST_EXPECT(checkArraySize(affected, 8u))) + return; + auto created = affected[5u][sfCreatedNode.fieldName]; + BEAST_EXPECT(created[sfNewFields.fieldName][jss::Account] == A2.human()); + env.close(); + + // test: offer was removed by offer_create + offers = getAccountOffers(env, A4)[jss::offers]; + if(! BEAST_EXPECT(checkArraySize(offers, 0u))) + return; + } + +public: + + void run() + { + testRippleState(); + testGlobalFreeze(); + testNoFreeze(); + testOffersWhenFrozen(); + } +}; + +BEAST_DEFINE_TESTSUITE(Freeze, app, ripple); +} // ripple + + diff --git a/src/unity/app_test_unity.cpp b/src/unity/app_test_unity.cpp index 2ca1a1166..050479e70 100644 --- a/src/unity/app_test_unity.cpp +++ b/src/unity/app_test_unity.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include