Compare commits

...

9 Commits

Author SHA1 Message Date
Richard Holland
3a6f5faacd clangf 2023-12-20 11:35:51 +00:00
Richard Holland
4eba415e9b . 2023-12-20 11:19:07 +00:00
Richard Holland
9d73b12713 . 2023-12-20 11:15:57 +00:00
Richard Holland
d3d4f4dbad lite fixes for tsh issues 2023-12-20 11:14:20 +00:00
RichardAH
dd3b0447d5 FXV1: allow duplicate entries in genesis mint transactor (#239) 2023-12-20 11:23:11 +01:00
Denis Angell
bba014a27c Merge branch 'dev' into fixXahauV1 2023-12-19 20:47:53 +01:00
RichardAH
64a07e5539 move utf8 checking to header file (#241)
Co-authored-by: Denis Angell <dangell@transia.co>
2023-12-19 19:52:34 +01:00
Denis Angell
617c896514 Merge branch 'dev' into fixXahauV1 2023-12-19 10:09:41 +01:00
Denis Angell
c49b7a2301 Fix: Verify Emitted Result on GenesisMint tests (#242) 2023-12-19 10:08:51 +01:00
6 changed files with 540 additions and 203 deletions

View File

@@ -71,6 +71,8 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
return rv.read(keylet::nftoffer(*id));
};
bool const fixV1 = rv.rules().enabled(fixXahauV1);
switch (tt)
{
case ttIMPORT: {
@@ -97,19 +99,36 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
// the burner is the issuer and not the owner of the token
if (issuer == owner)
{
// pass, already a TSH
}
else if (*otxnAcc == owner)
break;
// pass, already a TSH
// new logic
if (fixV1)
{
// the owner burns their token, and the issuer is a weak TSH
ADD_TSH(issuer, canRollback);
}
else
{
if (*otxnAcc == owner && rv.exists(keylet::account(issuer)))
ADD_TSH(issuer, false);
// the issuer burns the owner's token, and the owner is a weak
// TSH
ADD_TSH(owner, canRollback);
else if (rv.exists(keylet::account(owner)))
ADD_TSH(owner, false);
break;
}
// old logic
{
if (*otxnAcc == owner)
{
// the owner burns their token, and the issuer is a weak TSH
ADD_TSH(issuer, true);
}
else
{
// the issuer burns the owner's token, and the owner is a
// weak TSH
ADD_TSH(owner, true);
}
}
break;
@@ -300,19 +319,59 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
case ttESCROW_CANCEL:
case ttESCROW_FINISH: {
if (!tx.isFieldPresent(sfOwner) ||
!tx.isFieldPresent(sfOfferSequence))
return {};
// new logic
if (fixV1)
{
if (!tx.isFieldPresent(sfOwner))
return {};
auto escrow = rv.read(keylet::escrow(
tx.getAccountID(sfOwner), tx.getFieldU32(sfOfferSequence)));
AccountID const owner = tx.getAccountID(sfOwner);
if (!escrow)
return {};
bool const hasSeq = tx.isFieldPresent(sfOfferSequence);
bool const hasID = tx.isFieldPresent(sfEscrowID);
if (!hasSeq && !hasID)
return {};
ADD_TSH(escrow->getAccountID(sfAccount), true);
ADD_TSH(escrow->getAccountID(sfDestination), canRollback);
break;
Keylet kl = hasSeq
? keylet::escrow(owner, tx.getFieldU32(sfOfferSequence))
: Keylet(ltESCROW, tx.getFieldH256(sfEscrowID));
auto escrow = rv.read(kl);
if (!escrow ||
escrow->getFieldU16(sfLedgerEntryType) != ltESCROW)
return {};
// this should always be the same as owner, but defensively...
AccountID const src = escrow->getAccountID(sfAccount);
AccountID const dst = escrow->getAccountID(sfDestination);
// the source account is a strong transacitonal stakeholder for
// fin and can
ADD_TSH(src, true);
// the dest acc is a strong tsh for fin and weak for can
if (src != dst)
ADD_TSH(dst, tt == ttESCROW_FINISH);
break;
}
// old logic
{
if (!tx.isFieldPresent(sfOwner) ||
!tx.isFieldPresent(sfOfferSequence))
return {};
auto escrow = rv.read(keylet::escrow(
tx.getAccountID(sfOwner), tx.getFieldU32(sfOfferSequence)));
if (!escrow)
return {};
ADD_TSH(escrow->getAccountID(sfAccount), true);
ADD_TSH(escrow->getAccountID(sfDestination), canRollback);
break;
}
}
case ttPAYCHAN_FUND:

View File

@@ -74,6 +74,8 @@ GenesisMint::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
bool const allowDuplicates = ctx.rules.enabled(fixXahauV1);
std::unordered_set<AccountID> alreadySeen;
for (auto const& dest : dests)
{
@@ -137,6 +139,9 @@ GenesisMint::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (allowDuplicates)
continue;
if (alreadySeen.find(accid) != alreadySeen.end())
{
JLOG(ctx.j.warn()) << "GenesisMint: duplicate in destinations.";
@@ -171,7 +176,114 @@ GenesisMint::doApply()
{
auto const& dests = ctx_.tx.getFieldArray(sfGenesisMints);
if (!view().rules().enabled(fixXahauV1))
{
STAmount dropsAdded{0};
for (auto const& dest : dests)
{
auto const amt = dest[~sfAmount];
if (amt && !isXRP(*amt))
{
JLOG(ctx_.journal.warn()) << "GenesisMint: Non-xrp amount.";
return tecINTERNAL;
}
auto const flags = dest[~sfGovernanceFlags];
auto const marks = dest[~sfGovernanceMarks];
auto const id = dest.getAccountID(sfDestination);
auto const k = keylet::account(id);
auto sle = view().peek(k);
bool const created = !sle;
if (created)
{
// Create the account.
std::uint32_t const seqno{
view().info().parentCloseTime.time_since_epoch().count()};
sle = std::make_shared<SLE>(k);
sle->setAccountID(sfAccount, id);
sle->setFieldU32(sfSequence, seqno);
if (amt)
{
sle->setFieldAmount(sfBalance, *amt);
dropsAdded += *amt;
}
else // give them 2 XRP if the account didn't exist, same as
// ttIMPORT
{
XRPAmount const initialXrp =
Import::computeStartingBonus(ctx_.view());
sle->setFieldAmount(sfBalance, initialXrp);
dropsAdded += initialXrp;
}
}
else if (amt)
{
// Credit the account
STAmount startBal = sle->getFieldAmount(sfBalance);
STAmount finalBal = startBal + *amt;
if (finalBal <= startBal)
{
JLOG(ctx_.journal.warn())
<< "GenesisMint: cannot credit " << dest
<< " due to balance overflow";
return tecINTERNAL;
}
sle->setFieldAmount(sfBalance, finalBal);
dropsAdded += *amt;
}
// set flags and marks as applicable
if (flags)
sle->setFieldH256(sfGovernanceFlags, *flags);
if (marks)
sle->setFieldH256(sfGovernanceMarks, *marks);
if (created)
view().insert(sle);
else
view().update(sle);
}
// update ledger header
if (dropsAdded < beast::zero ||
dropsAdded.xrp() + view().info().drops < view().info().drops)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: dropsAdded overflowed\n";
return tecINTERNAL;
}
if (dropsAdded > beast::zero)
ctx_.rawView().rawDestroyXRP(-dropsAdded.xrp());
return tesSUCCESS;
}
// RH NOTE:
// As of fixXahauV1, duplicate accounts are allowed
// so we first do a summation loop then an actioning loop
// if amendment is not active then there's exactly one entry per
std::map<
AccountID, // account to credit
std::tuple<
XRPAmount, // amount to credit
std::optional<uint256>, // gov flags
std::optional<uint256>>> // gov marks
mints;
STAmount dropsAdded{0};
// sum loop
for (auto const& dest : dests)
{
auto const amt = dest[~sfAmount];
@@ -182,13 +294,54 @@ GenesisMint::doApply()
return tecINTERNAL;
}
XRPAmount toCredit = amt ? amt->xrp() : XRPAmount{0};
auto const flags = dest[~sfGovernanceFlags];
auto const marks = dest[~sfGovernanceMarks];
auto const id = dest.getAccountID(sfDestination);
auto const k = keylet::account(id);
auto sle = view().peek(k);
bool const firstOccurance = mints.find(id) == mints.end();
dropsAdded += toCredit;
// if flags / marks appear more than once we just take the first
// appearance
if (firstOccurance)
mints[id] = {toCredit, flags, marks};
else
{
// duplicated entries accumulate in the map
auto& [accTotal, f, m] = mints[id];
// detect overflow
if (accTotal + toCredit < accTotal)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: cannot credit " << id
<< " due to balance overflow";
return tecINTERNAL;
}
accTotal += toCredit;
}
}
// check for ledger header overflow
if (dropsAdded < beast::zero ||
dropsAdded.xrp() + view().info().drops < view().info().drops)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: dropsAdded overflowed\n";
return tecINTERNAL;
}
// action loop
for (auto const& [id, values] : mints)
{
auto const& [amt, flags, marks] = values;
auto const k = keylet::account(id);
auto sle = view().peek(k);
bool const created = !sle;
if (created)
@@ -202,34 +355,21 @@ GenesisMint::doApply()
sle->setFieldU32(sfSequence, seqno);
if (amt)
{
sle->setFieldAmount(sfBalance, *amt);
dropsAdded += *amt;
}
else // give them 2 XRP if the account didn't exist, same as
// ttIMPORT
{
XRPAmount const initialXrp =
Import::computeStartingBonus(ctx_.view());
sle->setFieldAmount(sfBalance, initialXrp);
dropsAdded += initialXrp;
}
sle->setFieldAmount(sfBalance, amt);
}
else if (amt)
else if (amt > beast::zero)
{
// Credit the account
STAmount startBal = sle->getFieldAmount(sfBalance);
STAmount finalBal = startBal + *amt;
STAmount finalBal = startBal + amt;
if (finalBal <= startBal)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: cannot credit "
<< dest << " due to balance overflow";
JLOG(ctx_.journal.warn()) << "GenesisMint: cannot credit " << id
<< " due to balance overflow";
return tecINTERNAL;
}
sle->setFieldAmount(sfBalance, finalBal);
dropsAdded += *amt;
}
// set flags and marks as applicable
@@ -245,14 +385,6 @@ GenesisMint::doApply()
view().update(sle);
}
// update ledger header
if (dropsAdded < beast::zero ||
dropsAdded.xrp() + view().info().drops < view().info().drops)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: dropsAdded overflowed\n";
return tecINTERNAL;
}
if (dropsAdded > beast::zero)
ctx_.rawView().rawDestroyXRP(-dropsAdded.xrp());

View File

@@ -103,57 +103,7 @@ URIToken::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (!([](std::vector<uint8_t> const& u) -> bool {
// this code is from
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
uint8_t const* s = (uint8_t const*)u.data();
uint8_t const* end = s + u.size();
while (s < end)
{
if (*s < 0x80)
/* 0xxxxxxx */
s++;
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 &&
(s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed &&
(s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 &&
(s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) ||
s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
return false;
}
return true;
})(uri))
if (!validateUTF8(uri))
{
JLOG(ctx.j.warn()) << "Malformed transaction. URI must be a "
"valid utf-8 string.";

View File

@@ -30,6 +30,56 @@ namespace ripple {
class URIToken : public Transactor
{
public:
bool inline static validateUTF8(std::vector<uint8_t> const& u)
{
// this code is from
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
uint8_t const* s = (uint8_t const*)u.data();
uint8_t const* end = s + u.size();
while (s < end)
{
if (*s < 0x80)
/* 0xxxxxxx */
s++;
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) ||
s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
return false;
}
return true;
}
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit URIToken(ApplyContext& ctx) : Transactor(ctx)

View File

@@ -24,6 +24,34 @@ namespace ripple {
namespace test {
struct GenesisMint_test : public beast::unit_test::suite
{
void
validateEmittedTxn(jtx::Env& env, std::string result, uint64_t lineno)
{
// get the emitted txn id
Json::Value params;
params[jss::transaction] =
env.tx()->getJson(JsonOptions::none)[jss::hash];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
auto const emissions = meta[sfHookEmissions.jsonName];
auto const emission = emissions[0u][sfHookEmission.jsonName];
auto const txId = emission[sfEmittedTxnID.jsonName];
env.close();
// verify emitted result
Json::Value params1;
params1[jss::transaction] = txId;
auto const jrr1 = env.rpc("json", "tx", to_string(params1));
auto const meta1 = jrr1[jss::result][jss::meta];
BEAST_EXPECT(meta1[sfTransactionResult.jsonName] == result);
if (meta1[sfTransactionResult.jsonName] != result)
{
std::cout << "validateEmittedTxn failed " << lineno
<< " expected: " << result
<< " result: " << meta1[sfTransactionResult.jsonName]
<< "\n";
}
}
void
testDisabled(FeatureBitset features)
{
@@ -106,10 +134,18 @@ struct GenesisMint_test : public beast::unit_test::suite
using namespace std::literals::chrono_literals;
Env env{*this, envconfig(), features, nullptr};
// Env env{*this, envconfig(), features, nullptr,
// // beast::severities::kWarning
// beast::severities::kTrace
// };
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const invoker = Account("invoker");
env.fund(XRP(10000), alice, bob, invoker);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const edward = Account("edward");
env.fund(XRP(10000), alice, bob, invoker, edward);
env.close();
// burn down the total ledger coins so that genesis mints don't mint
// above 100B tripping invariant
@@ -143,7 +179,9 @@ struct GenesisMint_test : public beast::unit_test::suite
ter(tesSUCCESS));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
{
auto acc = env.le(keylet::account(bob.id()));
@@ -182,21 +220,24 @@ struct GenesisMint_test : public beast::unit_test::suite
ter(tesSUCCESS));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
env.close();
{
auto acc = env.le(keylet::account(carol.id()));
BEAST_EXPECT(
acc->getFieldAmount(sfBalance).xrp().drops() == 67890000000ULL);
BEAST_EXPECT(acc->getFieldU32(sfSequence) == 50);
BEAST_EXPECT(acc->getFieldU32(sfSequence) == 60);
}
{
auto acc = env.le(keylet::account(david.id()));
BEAST_EXPECT(
acc->getFieldAmount(sfBalance).xrp().drops() == 12345000000ULL);
BEAST_EXPECT(acc->getFieldU32(sfSequence) == 50);
BEAST_EXPECT(acc->getFieldU32(sfSequence) == 60);
}
// lots of entries
@@ -221,7 +262,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
for (auto const& [acc, amt, _, __] : mints)
{
@@ -234,7 +277,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
for (auto const& [acc, amt, _, __] : mints)
{
@@ -267,9 +312,6 @@ struct GenesisMint_test : public beast::unit_test::suite
// invalid amount should cause emit fail which should cause hook
// rollback
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const edward = Account("edward");
env(invoke::invoke(
invoker,
env.master,
@@ -278,13 +320,24 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)),
ter(tecHOOK_REJECTED));
// zero xrp is allowed
env(invoke::invoke(
invoker,
env.master,
genesis::makeBlob(
{{edward.id(), XRP(0), std::nullopt, std::nullopt}})),
fee(XRP(1)));
// zero xrp is not allowed
{
env(invoke::invoke(
invoker,
env.master,
genesis::makeBlob(
{{edward.id(), XRP(0), std::nullopt, std::nullopt}})),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// validate emitted txn
auto const txResult = env.current()->rules().enabled(fixXahauV1)
? "tesSUCCESS"
: "tecINTERNAL";
validateEmittedTxn(env, txResult, __LINE__);
}
// missing an amount
env(invoke::invoke(
@@ -326,7 +379,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check that alice has the right balance, and Governance Flags set
@@ -350,7 +405,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check that bob has the right balance, and Governance Marks set
@@ -375,7 +432,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check
@@ -402,9 +461,12 @@ struct GenesisMint_test : public beast::unit_test::suite
XRP(0).value(),
std::nullopt,
std::nullopt}})),
fee(XRP(1)));
env.close();
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check
@@ -472,7 +534,9 @@ struct GenesisMint_test : public beast::unit_test::suite
std::nullopt}})),
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check
@@ -497,11 +561,19 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
auto const amtResult = env.current()->rules().enabled(fixXahauV1)
? 30000000ULL
: 10000000ULL;
// try to include the same destination twice
{
auto const txResult = env.current()->rules().enabled(fixXahauV1)
? ter(tesSUCCESS)
: ter(tecHOOK_REJECTED);
env(invoke::invoke(
invoker,
env.master,
@@ -516,7 +588,7 @@ struct GenesisMint_test : public beast::unit_test::suite
std::nullopt},
})),
fee(XRP(1)),
ter(tecHOOK_REJECTED));
txResult);
env.close();
env.close();
}
@@ -526,7 +598,7 @@ struct GenesisMint_test : public beast::unit_test::suite
auto const le = env.le(keylet::account(greg.id()));
BEAST_EXPECT(
!!le &&
le->getFieldAmount(sfBalance).xrp().drops() == 10000000ULL);
le->getFieldAmount(sfBalance).xrp().drops() == amtResult);
}
// trip the supply cap invariant
@@ -543,13 +615,15 @@ struct GenesisMint_test : public beast::unit_test::suite
})),
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tecINVARIANT_FAILED", __LINE__);
// check balance wasn't changed
auto const le = env.le(keylet::account(greg.id()));
BEAST_EXPECT(
!!le &&
le->getFieldAmount(sfBalance).xrp().drops() == 10000000ULL);
le->getFieldAmount(sfBalance).xrp().drops() == amtResult);
auto const postCoins = env.current()->info().drops;
BEAST_EXPECT(
@@ -602,92 +676,27 @@ struct GenesisMint_test : public beast::unit_test::suite
le->getFieldAmount(sfBalance).xrp().drops() == 10000000000ULL);
}
void
testGenesisMintTSH(FeatureBitset features)
{
testcase("GenesisMint TSH");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env{*this, envconfig(), features, nullptr};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const invoker = Account("invoker");
env.fund(XRP(10000), alice, bob, invoker);
// set tsh collect on bob
env(fset(bob, asfTshCollect));
// burn down the total ledger coins so that genesis mints don't mint
// above 100B tripping invariant
env(burn(env.master), fee(XRP(10'000'000ULL)));
env.close();
// set the test hook
env(genesis::setMintHook(env.master), fee(XRP(10)));
env.close();
// set the accept hook
env(genesis::setAcceptHook(bob), fee(XRP(10)));
env.close();
// test a mint
{
env(invoke::invoke(
invoker,
env.master,
genesis::makeBlob({
{bob.id(),
XRP(123).value(),
std::nullopt,
std::nullopt},
})),
fee(XRP(10)),
ter(tesSUCCESS));
env.close();
// get the emitted txn id
Json::Value params;
params[jss::transaction] =
env.tx()->getJson(JsonOptions::none)[jss::hash];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
auto const emissions = meta[sfHookEmissions.jsonName];
auto const emission = emissions[0u][sfHookEmission.jsonName];
auto const txId = emission[sfEmittedTxnID.jsonName];
// trigger the emitted txn
env.close();
// verify tsh hook triggered
Json::Value params1;
params1[jss::transaction] = txId;
auto const jrr1 = env.rpc("json", "tx", to_string(params1));
auto const meta1 = jrr1[jss::result][jss::meta];
auto const executions = meta1[sfHookExecutions.jsonName];
auto const execution = executions[0u][sfHookExecution.jsonName];
BEAST_EXPECT(execution[sfHookResult.jsonName] == 3);
}
}
public:
void
testWithFeats(FeatureBitset features)
{
testDisabled(features);
testGenesisEmit(features);
testGenesisNonEmit(features);
testNonGenesisEmit(features);
testNonGenesisNonEmit(features);
}
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
testDisabled(sa);
testGenesisEmit(sa);
testGenesisNonEmit(sa);
testNonGenesisEmit(sa);
testNonGenesisNonEmit(sa);
testGenesisMintTSH(sa);
testWithFeats(sa);
testWithFeats(sa - fixXahauV1);
}
};
BEAST_DEFINE_TESTSUITE(GenesisMint, app, ripple);
} // namespace test
} // namespace ripple
} // namespace ripple

View File

@@ -4245,6 +4245,142 @@ struct XahauGenesis_test : public beast::unit_test::suite
env, user, preLedger, preLedger + 1, postUser, preTime));
}
void
testValidL1WithUNLReport(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test claim reward valid for L1 with unl report");
for (bool const withXahauV1 : {true, false})
{
FeatureBitset _features = features - featureXahauGenesis;
auto const amend = withXahauV1 ? _features : _features - fixXahauV1;
Env env{*this, envconfig(), amend};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
// setup governance
Account const alice = Account("alice");
Account const bob = Account("bob");
Account const carol = Account("carol");
Account const david = Account("david");
Account const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// setup unl report
std::vector<std::string> const _ivlKeys = {
"ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1"
"CDC1",
};
std::vector<PublicKey> ivlKeys;
for (auto const& strPk : _ivlKeys)
{
auto pkHex = strUnHex(strPk);
ivlKeys.emplace_back(makeSlice(*pkHex));
}
std::vector<std::string> const _vlKeys = {
"0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97"
"C05"
"2",
"02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006"
"E3F"
"E",
"028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C0954"
"07B"
"F",
"027BAEF0CB02EA8B95F50DF4BC16C740B17B50C85F3757AA06A5DB6ADE0ED9"
"210"
"6",
"0318E0D644F3D2911D7B7E1B0B17684E7E625A6C36AECCE851BD16A4AD628B"
"213"
"6"};
std::vector<PublicKey> vlKeys;
for (auto const& strPk : _vlKeys)
{
auto pkHex = strUnHex(strPk);
vlKeys.emplace_back(makeSlice(*pkHex));
}
// activate unl report
activateUNLReport(env, ivlKeys, vlKeys);
// validate unl report
BEAST_EXPECT(hasUNLReport(env) == true);
// opt in claim reward
env(claimReward(alice, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// close ledgers
validateTime(lastClose(env), 90);
for (int i = 0; i < 5; ++i)
{
env.close(10s);
}
validateTime(lastClose(env), 180);
// define pre close values
STAmount const preAlice = env.balance(alice);
STAmount const preBob = env.balance(bob);
STAmount const preCarol = env.balance(carol);
STAmount const preDavid = env.balance(david);
STAmount const preEdward = env.balance(edward);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] =
accountKeyAndSle(*env.current(), alice);
// claim reward
auto const txResult =
withXahauV1 ? ter(tesSUCCESS) : ter(tecHOOK_REJECTED);
env(claimReward(alice, env.master), fee(feesXRP), txResult);
env.close();
// trigger emitted txn
env.close();
// calculate rewards
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
STAmount const l1Reward =
withXahauV1 ? rewardL1Amount(netReward, 20) : STAmount(0);
// validate govern rewards
BEAST_EXPECT(env.balance(bob) == preBob + l1Reward);
BEAST_EXPECT(env.balance(carol) == preCarol + l1Reward);
BEAST_EXPECT(env.balance(david) == preDavid + l1Reward);
BEAST_EXPECT(env.balance(edward) == preEdward + l1Reward);
// validate account fields
STAmount const postAlice = preAlice + netReward + l1Reward;
bool const boolResult = withXahauV1 ? true : false;
BEAST_EXPECT(
expectAccountFields(
env, alice, preLedger, preLedger + 1, postAlice, preTime) ==
boolResult);
}
}
void
testOptInOptOut(FeatureBitset features)
{
@@ -4873,6 +5009,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
testInvalidBeforeTime(features);
testValidWithoutUNLReport(features);
testValidWithUNLReport(features);
testValidL1WithUNLReport(features);
testOptInOptOut(features);
testValidLowBalance(features);
testValidElapsed1(features);