mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-28 06:25:49 +00:00
Compare commits
9 Commits
fxv1-tsh
...
litetshfix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a6f5faacd | ||
|
|
4eba415e9b | ||
|
|
9d73b12713 | ||
|
|
d3d4f4dbad | ||
|
|
dd3b0447d5 | ||
|
|
bba014a27c | ||
|
|
64a07e5539 | ||
|
|
617c896514 | ||
|
|
c49b7a2301 |
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user