mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Merge branch 'ximinez/lending-refactoring-2' into ximinez/lending-refactoring-3
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||||
// in include/xrpl/protocol/Feature.h.
|
// in include/xrpl/protocol/Feature.h.
|
||||||
|
|
||||||
|
XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
|
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo)
|
XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
@@ -39,7 +40,7 @@ XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo
|
|||||||
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
// Check flags in Credential transactions
|
// Check flags in Credential transactions
|
||||||
|
|||||||
@@ -678,6 +678,61 @@ private:
|
|||||||
oracle.set(
|
oracle.set(
|
||||||
UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee});
|
UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (bool const withFixOrder : {false, true})
|
||||||
|
{
|
||||||
|
// Should be same order as creation
|
||||||
|
Env env(
|
||||||
|
*this,
|
||||||
|
withFixOrder ? testable_amendments()
|
||||||
|
: testable_amendments() - fixPriceOracleOrder);
|
||||||
|
auto const baseFee =
|
||||||
|
static_cast<int>(env.current()->fees().base.drops());
|
||||||
|
|
||||||
|
auto test = [&](Env& env, DataSeries const& series) {
|
||||||
|
env.fund(XRP(1'000), owner);
|
||||||
|
Oracle oracle(
|
||||||
|
env, {.owner = owner, .series = series, .fee = baseFee});
|
||||||
|
BEAST_EXPECT(oracle.exists());
|
||||||
|
auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
sle->getFieldArray(sfPriceDataSeries).size() ==
|
||||||
|
series.size());
|
||||||
|
|
||||||
|
auto const beforeQuoteAssetName1 =
|
||||||
|
sle->getFieldArray(sfPriceDataSeries)[0]
|
||||||
|
.getFieldCurrency(sfQuoteAsset)
|
||||||
|
.getText();
|
||||||
|
auto const beforeQuoteAssetName2 =
|
||||||
|
sle->getFieldArray(sfPriceDataSeries)[1]
|
||||||
|
.getFieldCurrency(sfQuoteAsset)
|
||||||
|
.getText();
|
||||||
|
|
||||||
|
oracle.set(UpdateArg{.series = series, .fee = baseFee});
|
||||||
|
sle = env.le(keylet::oracle(owner, oracle.documentID()));
|
||||||
|
|
||||||
|
auto const afterQuoteAssetName1 =
|
||||||
|
sle->getFieldArray(sfPriceDataSeries)[0]
|
||||||
|
.getFieldCurrency(sfQuoteAsset)
|
||||||
|
.getText();
|
||||||
|
auto const afterQuoteAssetName2 =
|
||||||
|
sle->getFieldArray(sfPriceDataSeries)[1]
|
||||||
|
.getFieldCurrency(sfQuoteAsset)
|
||||||
|
.getText();
|
||||||
|
|
||||||
|
if (env.current()->rules().enabled(fixPriceOracleOrder))
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
|
||||||
|
BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1);
|
||||||
|
BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -19,223 +19,11 @@
|
|||||||
|
|
||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
|
|
||||||
#include <xrpl/beast/unit_test.h>
|
|
||||||
#include <xrpl/json/json_reader.h>
|
|
||||||
#include <xrpl/json/to_string.h>
|
|
||||||
#include <xrpl/protocol/SecretKey.h>
|
|
||||||
#include <xrpl/protocol/jss.h>
|
|
||||||
#include <xrpl/protocol/st.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class STObject_test : public beast::unit_test::suite
|
class STObject_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool
|
|
||||||
parseJSONString(std::string const& json, Json::Value& to)
|
|
||||||
{
|
|
||||||
Json::Reader reader;
|
|
||||||
return reader.parse(json, to) && to.isObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testParseJSONArrayWithInvalidChildrenObjects()
|
|
||||||
{
|
|
||||||
testcase("parse json array invalid children");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|
|
||||||
STArray/STObject constructs don't really map perfectly to json
|
|
||||||
arrays/objects.
|
|
||||||
|
|
||||||
STObject is an associative container, mapping fields to value, but
|
|
||||||
an STObject may also have a Field as its name, stored outside the
|
|
||||||
associative structure. The name is important, so to maintain
|
|
||||||
fidelity, it will take TWO json objects to represent them.
|
|
||||||
|
|
||||||
*/
|
|
||||||
std::string faulty(
|
|
||||||
"{\"Template\":[{"
|
|
||||||
"\"ModifiedNode\":{\"Sequence\":1}, "
|
|
||||||
"\"DeletedNode\":{\"Sequence\":1}"
|
|
||||||
"}]}");
|
|
||||||
|
|
||||||
std::unique_ptr<STObject> so;
|
|
||||||
Json::Value faultyJson;
|
|
||||||
bool parsedOK(parseJSONString(faulty, faultyJson));
|
|
||||||
unexpected(!parsedOK, "failed to parse");
|
|
||||||
STParsedJSONObject parsed("test", faultyJson);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
}
|
|
||||||
catch (std::runtime_error& e)
|
|
||||||
{
|
|
||||||
std::string what(e.what());
|
|
||||||
unexpected(what.find("First level children of `Template`") != 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testParseJSONArray()
|
|
||||||
{
|
|
||||||
testcase("parse json array");
|
|
||||||
std::string const json(
|
|
||||||
"{\"Template\":[{\"ModifiedNode\":{\"Sequence\":1}}]}");
|
|
||||||
|
|
||||||
Json::Value jsonObject;
|
|
||||||
bool parsedOK(parseJSONString(json, jsonObject));
|
|
||||||
if (parsedOK)
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jsonObject);
|
|
||||||
BEAST_EXPECT(parsed.object);
|
|
||||||
std::string const& serialized(
|
|
||||||
to_string(parsed.object->getJson(JsonOptions::none)));
|
|
||||||
BEAST_EXPECT(serialized == json);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fail("Couldn't parse json: " + json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testParseJSONEdgeCases()
|
|
||||||
{
|
|
||||||
testcase("parse json object");
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const goodJson(R"({"CloseResolution":19,"Method":250,)"
|
|
||||||
R"("TransactionResult":"tecFROZEN"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
if (BEAST_EXPECT(parsed.object))
|
|
||||||
{
|
|
||||||
std::string const& serialized(
|
|
||||||
to_string(parsed.object->getJson(JsonOptions::none)));
|
|
||||||
BEAST_EXPECT(serialized == goodJson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const goodJson(
|
|
||||||
R"({"CloseResolution":19,"Method":"250",)"
|
|
||||||
R"("TransactionResult":"tecFROZEN"})");
|
|
||||||
std::string const expectedJson(
|
|
||||||
R"({"CloseResolution":19,"Method":250,)"
|
|
||||||
R"("TransactionResult":"tecFROZEN"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
|
||||||
{
|
|
||||||
// Integer values are always parsed as int,
|
|
||||||
// unless they're too big. We want a small uint.
|
|
||||||
jv["CloseResolution"] = Json::UInt(19);
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
if (BEAST_EXPECT(parsed.object))
|
|
||||||
{
|
|
||||||
std::string const& serialized(
|
|
||||||
to_string(parsed.object->getJson(JsonOptions::none)));
|
|
||||||
BEAST_EXPECT(serialized == expectedJson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
|
||||||
R"("TransactionResult":"terQUEUED"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.TransactionResult' is out of range.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(R"({"CloseResolution":19,"Method":"pony",)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.Method' has bad type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(
|
|
||||||
R"({"CloseResolution":19,"Method":3294967296,)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.Method' is out of range.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(R"({"CloseResolution":-10,"Method":42,)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.CloseResolution' is out of range.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(
|
|
||||||
R"({"CloseResolution":19,"Method":3.141592653,)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.Method' has bad type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
testSerialization()
|
testSerialization()
|
||||||
{
|
{
|
||||||
@@ -730,9 +518,6 @@ public:
|
|||||||
|
|
||||||
testFields();
|
testFields();
|
||||||
testSerialization();
|
testSerialization();
|
||||||
testParseJSONArray();
|
|
||||||
testParseJSONArrayWithInvalidChildrenObjects();
|
|
||||||
testParseJSONEdgeCases();
|
|
||||||
testMalformed();
|
testMalformed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
295
src/test/protocol/STParsedJSON_test.cpp
Normal file
295
src/test/protocol/STParsedJSON_test.cpp
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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 <test/jtx.h>
|
||||||
|
|
||||||
|
#include <xrpl/json/json_reader.h>
|
||||||
|
#include <xrpl/protocol/st.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
class STParsedJSON_test : public beast::unit_test::suite
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool
|
||||||
|
parseJSONString(std::string const& json, Json::Value& to)
|
||||||
|
{
|
||||||
|
Json::Reader reader;
|
||||||
|
return reader.parse(json, to) && to.isObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testParseJSONArrayWithInvalidChildrenObjects()
|
||||||
|
{
|
||||||
|
testcase("parse json array invalid children");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|
||||||
|
STArray/STObject constructs don't really map perfectly to json
|
||||||
|
arrays/objects.
|
||||||
|
|
||||||
|
STObject is an associative container, mapping fields to value, but
|
||||||
|
an STObject may also have a Field as its name, stored outside the
|
||||||
|
associative structure. The name is important, so to maintain
|
||||||
|
fidelity, it will take TWO json objects to represent them.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::string faulty(
|
||||||
|
"{\"Template\":[{"
|
||||||
|
"\"ModifiedNode\":{\"Sequence\":1}, "
|
||||||
|
"\"DeletedNode\":{\"Sequence\":1}"
|
||||||
|
"}]}");
|
||||||
|
|
||||||
|
std::unique_ptr<STObject> so;
|
||||||
|
Json::Value faultyJson;
|
||||||
|
bool parsedOK(parseJSONString(faulty, faultyJson));
|
||||||
|
unexpected(!parsedOK, "failed to parse");
|
||||||
|
STParsedJSONObject parsed("test", faultyJson);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
}
|
||||||
|
catch (std::runtime_error& e)
|
||||||
|
{
|
||||||
|
std::string what(e.what());
|
||||||
|
unexpected(what.find("First level children of `Template`") != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testParseJSONArray()
|
||||||
|
{
|
||||||
|
testcase("parse json array");
|
||||||
|
std::string const json(
|
||||||
|
"{\"Template\":[{\"ModifiedNode\":{\"Sequence\":1}}]}");
|
||||||
|
|
||||||
|
Json::Value jsonObject;
|
||||||
|
bool parsedOK(parseJSONString(json, jsonObject));
|
||||||
|
if (parsedOK)
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jsonObject);
|
||||||
|
BEAST_EXPECT(parsed.object);
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == json);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fail("Couldn't parse json: " + json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testParseJSONEdgeCases()
|
||||||
|
{
|
||||||
|
testcase("parse json object");
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const goodJson(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
if (BEAST_EXPECT(parsed.object))
|
||||||
|
{
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == goodJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const goodJson(
|
||||||
|
R"({"CloseResolution":19,"Method":"250",)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
std::string const expectedJson(
|
||||||
|
R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
||||||
|
{
|
||||||
|
// Integer values are always parsed as int,
|
||||||
|
// unless they're too big. We want a small uint.
|
||||||
|
jv["CloseResolution"] = Json::UInt(19);
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
if (BEAST_EXPECT(parsed.object))
|
||||||
|
{
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == expectedJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransactionResult":"terQUEUED"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransactionResult' is out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":"pony",)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.Method' has bad type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(
|
||||||
|
R"({"CloseResolution":19,"Method":3294967296,)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.Method' is out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":-10,"Method":42,)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.CloseResolution' is out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(
|
||||||
|
R"({"CloseResolution":19,"Method":3.141592653,)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.Method' has bad type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":"65536"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransferFee' has invalid data.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":"Payment"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransferFee' has invalid data.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":true})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransferFee' has bad type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
run() override
|
||||||
|
{
|
||||||
|
// Instantiate a jtx::Env so debugLog writes are exercised.
|
||||||
|
test::jtx::Env env(*this);
|
||||||
|
testParseJSONArrayWithInvalidChildrenObjects();
|
||||||
|
testParseJSONArray();
|
||||||
|
testParseJSONEdgeCases();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BEAST_DEFINE_TESTSUITE(STParsedJSON, protocol, ripple);
|
||||||
|
|
||||||
|
} // namespace ripple
|
||||||
@@ -178,126 +178,18 @@ MPTokenAuthorize::createMPToken(
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER
|
|
||||||
MPTokenAuthorize::authorize(
|
|
||||||
ApplyView& view,
|
|
||||||
beast::Journal journal,
|
|
||||||
MPTAuthorizeArgs const& args)
|
|
||||||
{
|
|
||||||
auto const sleAcct = view.peek(keylet::account(args.account));
|
|
||||||
if (!sleAcct)
|
|
||||||
return tecINTERNAL;
|
|
||||||
|
|
||||||
// If the account that submitted the tx is a holder
|
|
||||||
// Note: `account_` is holder's account
|
|
||||||
// `holderID` is NOT used
|
|
||||||
if (!args.holderID)
|
|
||||||
{
|
|
||||||
// When a holder wants to unauthorize/delete a MPT, the ledger must
|
|
||||||
// - delete mptokenKey from owner directory
|
|
||||||
// - delete the MPToken
|
|
||||||
if (args.flags & tfMPTUnauthorize)
|
|
||||||
{
|
|
||||||
auto const mptokenKey =
|
|
||||||
keylet::mptoken(args.mptIssuanceID, args.account);
|
|
||||||
auto const sleMpt = view.peek(mptokenKey);
|
|
||||||
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
|
|
||||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
||||||
|
|
||||||
if (!view.dirRemove(
|
|
||||||
keylet::ownerDir(args.account),
|
|
||||||
(*sleMpt)[sfOwnerNode],
|
|
||||||
sleMpt->key(),
|
|
||||||
false))
|
|
||||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
||||||
|
|
||||||
adjustOwnerCount(view, sleAcct, -1, journal);
|
|
||||||
|
|
||||||
view.erase(sleMpt);
|
|
||||||
return tesSUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A potential holder wants to authorize/hold a mpt, the ledger must:
|
|
||||||
// - add the new mptokenKey to the owner directory
|
|
||||||
// - create the MPToken object for the holder
|
|
||||||
|
|
||||||
// The reserve that is required to create the MPToken. Note
|
|
||||||
// that although the reserve increases with every item
|
|
||||||
// an account owns, in the case of MPTokens we only
|
|
||||||
// *enforce* a reserve if the user owns more than two
|
|
||||||
// items. This is similar to the reserve requirements of trust lines.
|
|
||||||
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
|
|
||||||
XRPAmount const reserveCreate(
|
|
||||||
(uOwnerCount < 2) ? XRPAmount(beast::zero)
|
|
||||||
: view.fees().accountReserve(uOwnerCount + 1));
|
|
||||||
|
|
||||||
if (args.priorBalance < reserveCreate)
|
|
||||||
return tecINSUFFICIENT_RESERVE;
|
|
||||||
|
|
||||||
auto const mptokenKey =
|
|
||||||
keylet::mptoken(args.mptIssuanceID, args.account);
|
|
||||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
|
||||||
if (auto ter = dirLink(view, args.account, mptoken))
|
|
||||||
return ter; // LCOV_EXCL_LINE
|
|
||||||
|
|
||||||
(*mptoken)[sfAccount] = args.account;
|
|
||||||
(*mptoken)[sfMPTokenIssuanceID] = args.mptIssuanceID;
|
|
||||||
(*mptoken)[sfFlags] = 0;
|
|
||||||
view.insert(mptoken);
|
|
||||||
|
|
||||||
// Update owner count.
|
|
||||||
adjustOwnerCount(view, sleAcct, 1, journal);
|
|
||||||
|
|
||||||
return tesSUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const sleMptIssuance =
|
|
||||||
view.read(keylet::mptIssuance(args.mptIssuanceID));
|
|
||||||
if (!sleMptIssuance)
|
|
||||||
return tecINTERNAL;
|
|
||||||
|
|
||||||
// If the account that submitted this tx is the issuer of the MPT
|
|
||||||
// Note: `account_` is issuer's account
|
|
||||||
// `holderID` is holder's account
|
|
||||||
if (args.account != (*sleMptIssuance)[sfIssuer])
|
|
||||||
return tecINTERNAL;
|
|
||||||
|
|
||||||
auto const sleMpt =
|
|
||||||
view.peek(keylet::mptoken(args.mptIssuanceID, *args.holderID));
|
|
||||||
if (!sleMpt)
|
|
||||||
return tecINTERNAL;
|
|
||||||
|
|
||||||
std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
|
|
||||||
std::uint32_t flagsOut = flagsIn;
|
|
||||||
|
|
||||||
// Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
|
|
||||||
// their MPToken
|
|
||||||
if (args.flags & tfMPTUnauthorize)
|
|
||||||
flagsOut &= ~lsfMPTAuthorized;
|
|
||||||
// Issuer wants to authorize a holder, set lsfMPTAuthorized on their
|
|
||||||
// MPToken
|
|
||||||
else
|
|
||||||
flagsOut |= lsfMPTAuthorized;
|
|
||||||
|
|
||||||
if (flagsIn != flagsOut)
|
|
||||||
sleMpt->setFieldU32(sfFlags, flagsOut);
|
|
||||||
|
|
||||||
view.update(sleMpt);
|
|
||||||
return tesSUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
TER
|
TER
|
||||||
MPTokenAuthorize::doApply()
|
MPTokenAuthorize::doApply()
|
||||||
{
|
{
|
||||||
auto const& tx = ctx_.tx;
|
auto const& tx = ctx_.tx;
|
||||||
return authorize(
|
return authorizeMPToken(
|
||||||
ctx_.view(),
|
ctx_.view(),
|
||||||
|
mPriorBalance,
|
||||||
|
tx[sfMPTokenIssuanceID],
|
||||||
|
account_,
|
||||||
ctx_.journal,
|
ctx_.journal,
|
||||||
{.priorBalance = mPriorBalance,
|
tx.getFlags(),
|
||||||
.mptIssuanceID = tx[sfMPTokenIssuanceID],
|
tx[~sfHolder]);
|
||||||
.account = account_,
|
|
||||||
.flags = tx.getFlags(),
|
|
||||||
.holderID = tx[~sfHolder]});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -54,12 +54,6 @@ public:
|
|||||||
static TER
|
static TER
|
||||||
preclaim(PreclaimContext const& ctx);
|
preclaim(PreclaimContext const& ctx);
|
||||||
|
|
||||||
static TER
|
|
||||||
authorize(
|
|
||||||
ApplyView& view,
|
|
||||||
beast::Journal journal,
|
|
||||||
MPTAuthorizeArgs const& args);
|
|
||||||
|
|
||||||
static TER
|
static TER
|
||||||
createMPToken(
|
createMPToken(
|
||||||
ApplyView& view,
|
ApplyView& view,
|
||||||
|
|||||||
@@ -206,6 +206,17 @@ SetOracle::doApply()
|
|||||||
{
|
{
|
||||||
auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
|
auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
|
||||||
|
|
||||||
|
auto populatePriceData = [](STObject& priceData, STObject const& entry) {
|
||||||
|
setPriceDataInnerObjTemplate(priceData);
|
||||||
|
priceData.setFieldCurrency(
|
||||||
|
sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
|
||||||
|
priceData.setFieldCurrency(
|
||||||
|
sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
|
||||||
|
priceData.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
|
||||||
|
if (entry.isFieldPresent(sfScale))
|
||||||
|
priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
|
||||||
|
};
|
||||||
|
|
||||||
if (auto sle = ctx_.view().peek(oracleID))
|
if (auto sle = ctx_.view().peek(oracleID))
|
||||||
{
|
{
|
||||||
// update
|
// update
|
||||||
@@ -246,15 +257,7 @@ SetOracle::doApply()
|
|||||||
{
|
{
|
||||||
// add a token pair with the price
|
// add a token pair with the price
|
||||||
STObject priceData{sfPriceData};
|
STObject priceData{sfPriceData};
|
||||||
setPriceDataInnerObjTemplate(priceData);
|
populatePriceData(priceData, entry);
|
||||||
priceData.setFieldCurrency(
|
|
||||||
sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
|
|
||||||
priceData.setFieldCurrency(
|
|
||||||
sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
|
|
||||||
priceData.setFieldU64(
|
|
||||||
sfAssetPrice, entry.getFieldU64(sfAssetPrice));
|
|
||||||
if (entry.isFieldPresent(sfScale))
|
|
||||||
priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
|
|
||||||
pairs.emplace(key, std::move(priceData));
|
pairs.emplace(key, std::move(priceData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +285,26 @@ SetOracle::doApply()
|
|||||||
sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
|
sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
|
||||||
if (ctx_.tx.isFieldPresent(sfURI))
|
if (ctx_.tx.isFieldPresent(sfURI))
|
||||||
sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
|
sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
|
||||||
auto const& series = ctx_.tx.getFieldArray(sfPriceDataSeries);
|
|
||||||
|
STArray series;
|
||||||
|
if (!ctx_.view().rules().enabled(fixPriceOracleOrder))
|
||||||
|
{
|
||||||
|
series = ctx_.tx.getFieldArray(sfPriceDataSeries);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::map<std::pair<Currency, Currency>, STObject> pairs;
|
||||||
|
for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
|
||||||
|
{
|
||||||
|
auto const key = tokenPairKey(entry);
|
||||||
|
STObject priceData{sfPriceData};
|
||||||
|
populatePriceData(priceData, entry);
|
||||||
|
pairs.emplace(key, std::move(priceData));
|
||||||
|
}
|
||||||
|
for (auto const& iter : pairs)
|
||||||
|
series.push_back(std::move(iter.second));
|
||||||
|
}
|
||||||
|
|
||||||
sle->setFieldArray(sfPriceDataSeries, series);
|
sle->setFieldArray(sfPriceDataSeries, series);
|
||||||
sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
|
sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
|
||||||
sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
|
sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
|
||||||
|
|||||||
@@ -207,12 +207,12 @@ VaultDeposit::doApply()
|
|||||||
auto sleMpt = view().read(keylet::mptoken(mptIssuanceID, account_));
|
auto sleMpt = view().read(keylet::mptoken(mptIssuanceID, account_));
|
||||||
if (!sleMpt)
|
if (!sleMpt)
|
||||||
{
|
{
|
||||||
if (auto const err = MPTokenAuthorize::authorize(
|
if (auto const err = authorizeMPToken(
|
||||||
view(),
|
view(),
|
||||||
ctx_.journal,
|
mPriorBalance,
|
||||||
{.priorBalance = mPriorBalance,
|
mptIssuanceID->value(),
|
||||||
.mptIssuanceID = mptIssuanceID->value(),
|
account_,
|
||||||
.account = account_});
|
ctx_.journal);
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -220,15 +220,15 @@ VaultDeposit::doApply()
|
|||||||
// If the vault is private, set the authorized flag for the vault owner
|
// If the vault is private, set the authorized flag for the vault owner
|
||||||
if (vault->isFlag(tfVaultPrivate))
|
if (vault->isFlag(tfVaultPrivate))
|
||||||
{
|
{
|
||||||
if (auto const err = MPTokenAuthorize::authorize(
|
if (auto const err = authorizeMPToken(
|
||||||
view(),
|
view(),
|
||||||
|
mPriorBalance, // priorBalance
|
||||||
|
mptIssuanceID->value(), // mptIssuanceID
|
||||||
|
sleIssuance->at(sfIssuer), // account
|
||||||
ctx_.journal,
|
ctx_.journal,
|
||||||
{
|
{}, // flags
|
||||||
.priorBalance = mPriorBalance,
|
account_ // holderID
|
||||||
.mptIssuanceID = mptIssuanceID->value(),
|
);
|
||||||
.account = sleIssuance->at(sfIssuer),
|
|
||||||
.holderID = account_,
|
|
||||||
});
|
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -616,6 +616,16 @@ addEmptyHolding(
|
|||||||
asset.value());
|
asset.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] TER
|
||||||
|
authorizeMPToken(
|
||||||
|
ApplyView& view,
|
||||||
|
XRPAmount const& priorBalance,
|
||||||
|
MPTID const& mptIssuanceID,
|
||||||
|
AccountID const& account,
|
||||||
|
beast::Journal journal,
|
||||||
|
std::uint32_t flags = 0,
|
||||||
|
std::optional<AccountID> holderID = std::nullopt);
|
||||||
|
|
||||||
// VFALCO NOTE Both STAmount parameters should just
|
// VFALCO NOTE Both STAmount parameters should just
|
||||||
// be "Amount", a unit-less number.
|
// be "Amount", a unit-less number.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
|
||||||
#include <xrpld/ledger/ReadView.h>
|
#include <xrpld/ledger/ReadView.h>
|
||||||
#include <xrpld/ledger/View.h>
|
#include <xrpld/ledger/View.h>
|
||||||
|
|
||||||
@@ -1272,12 +1271,115 @@ addEmptyHolding(
|
|||||||
if (view.peek(keylet::mptoken(mptID, accountID)))
|
if (view.peek(keylet::mptoken(mptID, accountID)))
|
||||||
return tecDUPLICATE;
|
return tecDUPLICATE;
|
||||||
|
|
||||||
return MPTokenAuthorize::authorize(
|
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
|
||||||
view,
|
}
|
||||||
journal,
|
|
||||||
{.priorBalance = priorBalance,
|
[[nodiscard]] TER
|
||||||
.mptIssuanceID = mptID,
|
authorizeMPToken(
|
||||||
.account = accountID});
|
ApplyView& view,
|
||||||
|
XRPAmount const& priorBalance,
|
||||||
|
MPTID const& mptIssuanceID,
|
||||||
|
AccountID const& account,
|
||||||
|
beast::Journal journal,
|
||||||
|
std::uint32_t flags,
|
||||||
|
std::optional<AccountID> holderID)
|
||||||
|
{
|
||||||
|
auto const sleAcct = view.peek(keylet::account(account));
|
||||||
|
if (!sleAcct)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
// If the account that submitted the tx is a holder
|
||||||
|
// Note: `account_` is holder's account
|
||||||
|
// `holderID` is NOT used
|
||||||
|
if (!holderID)
|
||||||
|
{
|
||||||
|
// When a holder wants to unauthorize/delete a MPT, the ledger must
|
||||||
|
// - delete mptokenKey from owner directory
|
||||||
|
// - delete the MPToken
|
||||||
|
if (flags & tfMPTUnauthorize)
|
||||||
|
{
|
||||||
|
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||||
|
auto const sleMpt = view.peek(mptokenKey);
|
||||||
|
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
|
||||||
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||||
|
|
||||||
|
if (!view.dirRemove(
|
||||||
|
keylet::ownerDir(account),
|
||||||
|
(*sleMpt)[sfOwnerNode],
|
||||||
|
sleMpt->key(),
|
||||||
|
false))
|
||||||
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||||
|
|
||||||
|
adjustOwnerCount(view, sleAcct, -1, journal);
|
||||||
|
|
||||||
|
view.erase(sleMpt);
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A potential holder wants to authorize/hold a mpt, the ledger must:
|
||||||
|
// - add the new mptokenKey to the owner directory
|
||||||
|
// - create the MPToken object for the holder
|
||||||
|
|
||||||
|
// The reserve that is required to create the MPToken. Note
|
||||||
|
// that although the reserve increases with every item
|
||||||
|
// an account owns, in the case of MPTokens we only
|
||||||
|
// *enforce* a reserve if the user owns more than two
|
||||||
|
// items. This is similar to the reserve requirements of trust lines.
|
||||||
|
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
|
||||||
|
XRPAmount const reserveCreate(
|
||||||
|
(uOwnerCount < 2) ? XRPAmount(beast::zero)
|
||||||
|
: view.fees().accountReserve(uOwnerCount + 1));
|
||||||
|
|
||||||
|
if (priorBalance < reserveCreate)
|
||||||
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
|
||||||
|
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||||
|
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||||
|
if (auto ter = dirLink(view, account, mptoken))
|
||||||
|
return ter; // LCOV_EXCL_LINE
|
||||||
|
|
||||||
|
(*mptoken)[sfAccount] = account;
|
||||||
|
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
|
||||||
|
(*mptoken)[sfFlags] = 0;
|
||||||
|
view.insert(mptoken);
|
||||||
|
|
||||||
|
// Update owner count.
|
||||||
|
adjustOwnerCount(view, sleAcct, 1, journal);
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||||
|
if (!sleMptIssuance)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
// If the account that submitted this tx is the issuer of the MPT
|
||||||
|
// Note: `account_` is issuer's account
|
||||||
|
// `holderID` is holder's account
|
||||||
|
if (account != (*sleMptIssuance)[sfIssuer])
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
|
||||||
|
if (!sleMpt)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
|
||||||
|
std::uint32_t flagsOut = flagsIn;
|
||||||
|
|
||||||
|
// Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
|
||||||
|
// their MPToken
|
||||||
|
if (flags & tfMPTUnauthorize)
|
||||||
|
flagsOut &= ~lsfMPTAuthorized;
|
||||||
|
// Issuer wants to authorize a holder, set lsfMPTAuthorized on their
|
||||||
|
// MPToken
|
||||||
|
else
|
||||||
|
flagsOut |= lsfMPTAuthorized;
|
||||||
|
|
||||||
|
if (flagsIn != flagsOut)
|
||||||
|
sleMpt->setFieldU32(sfFlags, flagsOut);
|
||||||
|
|
||||||
|
view.update(sleMpt);
|
||||||
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER
|
TER
|
||||||
@@ -1475,13 +1577,14 @@ removeEmptyHolding(
|
|||||||
if (mptoken->at(sfMPTAmount) != 0)
|
if (mptoken->at(sfMPTAmount) != 0)
|
||||||
return tecHAS_OBLIGATIONS;
|
return tecHAS_OBLIGATIONS;
|
||||||
|
|
||||||
return MPTokenAuthorize::authorize(
|
return authorizeMPToken(
|
||||||
view,
|
view,
|
||||||
|
{}, // priorBalance
|
||||||
|
mptID,
|
||||||
|
accountID,
|
||||||
journal,
|
journal,
|
||||||
{.priorBalance = {},
|
tfMPTUnauthorize // flags
|
||||||
.mptIssuanceID = mptID,
|
);
|
||||||
.account = accountID,
|
|
||||||
.flags = tfMPTUnauthorize});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TER
|
TER
|
||||||
@@ -2554,15 +2657,12 @@ enforceMPTokenAuthorization(
|
|||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
maybeDomainID.has_value() && sleToken == nullptr,
|
maybeDomainID.has_value() && sleToken == nullptr,
|
||||||
"ripple::enforceMPTokenAuthorization : new MPToken for domain");
|
"ripple::enforceMPTokenAuthorization : new MPToken for domain");
|
||||||
if (auto const err = MPTokenAuthorize::authorize(
|
if (auto const err = authorizeMPToken(
|
||||||
view,
|
view,
|
||||||
j,
|
priorBalance, // priorBalance
|
||||||
{
|
mptIssuanceID, // mptIssuanceID
|
||||||
.priorBalance = priorBalance,
|
account, // account
|
||||||
.mptIssuanceID = mptIssuanceID,
|
j);
|
||||||
.account = account,
|
|
||||||
.flags = 0,
|
|
||||||
});
|
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user