add tests for CronSet

This commit is contained in:
tequ
2025-10-15 15:14:59 +09:00
parent 0d7dd0597d
commit ac1bf88596
8 changed files with 427 additions and 26 deletions

View File

@@ -736,6 +736,7 @@ if (tests)
src/test/app/BaseFee_test.cpp src/test/app/BaseFee_test.cpp
src/test/app/Check_test.cpp src/test/app/Check_test.cpp
src/test/app/ClaimReward_test.cpp src/test/app/ClaimReward_test.cpp
src/test/app/Cron_test.cpp
src/test/app/Clawback_test.cpp src/test/app/Clawback_test.cpp
src/test/app/CrossingLimits_test.cpp src/test/app/CrossingLimits_test.cpp
src/test/app/DeliverMin_test.cpp src/test/app/DeliverMin_test.cpp
@@ -900,6 +901,7 @@ if (tests)
src/test/jtx/impl/amount.cpp src/test/jtx/impl/amount.cpp
src/test/jtx/impl/balance.cpp src/test/jtx/impl/balance.cpp
src/test/jtx/impl/check.cpp src/test/jtx/impl/check.cpp
src/test/jtx/impl/cron.cpp
src/test/jtx/impl/delivermin.cpp src/test/jtx/impl/delivermin.cpp
src/test/jtx/impl/deposit.cpp src/test/jtx/impl/deposit.cpp
src/test/jtx/impl/envconfig.cpp src/test/jtx/impl/envconfig.cpp

View File

@@ -491,6 +491,7 @@ LedgerEntryTypesMatch::visitEntry(
case ltNFTOKEN_PAGE: case ltNFTOKEN_PAGE:
case ltNFTOKEN_OFFER: case ltNFTOKEN_OFFER:
case ltURI_TOKEN: case ltURI_TOKEN:
case ltCRON:
case ltIMPORT_VLSEQ: case ltIMPORT_VLSEQ:
case ltUNL_REPORT: case ltUNL_REPORT:
break; break;

View File

@@ -19,12 +19,9 @@
#include <ripple/app/tx/impl/SetCron.h> #include <ripple/app/tx/impl/SetCron.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/ledger/View.h> #include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h> #include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/Quality.h>
#include <ripple/protocol/TxFlags.h> #include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h> #include <ripple/protocol/st.h>
@@ -75,7 +72,7 @@ SetCron::preflight(PreflightContext const& ctx)
auto delay = tx.getFieldU32(sfDelaySeconds); auto delay = tx.getFieldU32(sfDelaySeconds);
if (delay > 31536000UL /* 365 days in seconds */) if (delay > 31536000UL /* 365 days in seconds */)
{ {
JLOG(j.debug()) << "SetCron: DelaySeconds was too high. (max 14 " JLOG(j.debug()) << "SetCron: DelaySeconds was too high. (max 365 "
"days in seconds)."; "days in seconds).";
return temMALFORMED; return temMALFORMED;
} }
@@ -99,18 +96,6 @@ SetCron::preflight(PreflightContext const& ctx)
TER TER
SetCron::preclaim(PreclaimContext const& ctx) SetCron::preclaim(PreclaimContext const& ctx)
{ {
if (!ctx.view.rules().enabled(featureCron))
return temDISABLED;
auto& j = ctx.j;
auto const id = ctx.tx[sfAccount];
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
return tesSUCCESS; return tesSUCCESS;
} }
@@ -244,22 +229,27 @@ SetCron::doApply()
XRPAmount XRPAmount
SetCron::calculateBaseFee(ReadView const& view, STTx const& tx) SetCron::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
auto fee = Transactor::calculateBaseFee(view, tx); auto const baseFee = Transactor::calculateBaseFee(view, tx);
auto const hasRepeat = tx.isFieldPresent(sfRepeatCount);
auto const hasDelay = tx.isFieldPresent(sfDelaySeconds);
if (!hasRepeat && !hasDelay)
// delete cron
return baseFee;
// factor a cost based on the total number of txns expected // factor a cost based on the total number of txns expected
// for RepeatCount of 0 we have this txn (SetCron) and the // for RepeatCount of 0 we have this txn (SetCron) and the
// single Cron txn (2). For a RepeatCount of 1 we have this txn, // single Cron txn (2). For a RepeatCount of 1 we have this txn,
// the first time the cron executes, and the second time (3). // the first time the cron executes, and the second time (3).
uint32_t recur = tx.isFieldPresent(sfRepeatCount) uint32_t const additionalExpectedExecutions =
? tx.getFieldU32(sfRepeatCount) + 2 tx.getFieldU32(sfRepeatCount) + 1;
: 2; auto const additionalFee = baseFee * additionalExpectedExecutions;
auto finalFee = fee * recur; if (baseFee + additionalFee < baseFee)
return baseFee;
if (finalFee < fee) return baseFee + additionalFee;
return fee;
return finalFee;
} }
} // namespace ripple } // namespace ripple

View File

@@ -22,7 +22,6 @@
#include <ripple/app/tx/impl/Transactor.h> #include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
namespace ripple { namespace ripple {

278
src/test/app/Cron_test.cpp Normal file
View File

@@ -0,0 +1,278 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 XRPL Labs
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 <ripple/app/ledger/LedgerMaster.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/jss.h>
#include "ripple/protocol/Indexes.h"
#include "ripple/protocol/TER.h"
#include "ripple/protocol/TxFlags.h"
#include "test/jtx/TestHelpers.h"
#include "test/jtx/cron.h"
#include <test/jtx.h>
namespace ripple {
namespace test {
struct Cron_test : public beast::unit_test::suite
{
void
testEnabled(FeatureBitset features)
{
testcase("enabled");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
auto const alice = Account("alice");
auto const issuer = Account("issuer");
for (bool const withCron : {false, true})
{
// If the BalanceRewards amendment is not enabled, you should not be
// able to claim rewards.
auto const amend = withCron ? features : features - featureCron;
Env env{*this, amend};
env.fund(XRP(1000), alice, issuer);
env.close();
auto const expectResult =
withCron ? ter(tesSUCCESS) : ter(temDISABLED);
auto tx = cron::set(alice);
// CLAIM
env(cron::set(alice), fee(XRP(1)), expectResult);
env.close();
}
}
void
testFee(FeatureBitset features)
{
testcase("fee");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account("alice");
Env env{*this, features | featureCron};
auto const baseFee = env.current()->fees().base;
env.fund(XRP(1000), alice);
env.close();
// create with RepeatCount
auto expected = baseFee * 2 + baseFee * 256;
env(cron::set(alice),
cron::delay(356 * 24 * 60 * 60),
cron::repeat(256),
fee(expected - 1),
ter(telINSUF_FEE_P));
env.close();
env(cron::set(alice),
cron::delay(356 * 24 * 60 * 60),
cron::repeat(256),
fee(expected),
ter(tesSUCCESS));
env.close();
// create with no RepeatCount
expected = baseFee * 2;
env(cron::set(alice),
cron::delay(356 * 24 * 60 * 60),
fee(expected - 1),
ter(telINSUF_FEE_P));
env.close();
env(cron::set(alice),
cron::delay(356 * 24 * 60 * 60),
fee(expected),
ter(tesSUCCESS));
env.close();
// delete
expected = baseFee;
env(cron::set(alice), fee(expected - 1), ter(telINSUF_FEE_P));
env.close();
env(cron::set(alice), fee(expected), ter(tesSUCCESS));
env.close();
}
void
testInvalidPreflight(FeatureBitset features)
{
testcase("invalid preflight");
using namespace test::jtx;
using namespace std::literals;
auto const alice = Account("alice");
test::jtx::Env env{
*this, network::makeNetworkConfig(21337), features | featureCron};
env.fund(XRP(1000), alice);
env.close();
//----------------------------------------------------------------------
// preflight
// temINVALID_FLAG
// can have flag 1 set to opt-out of rewards
{
env(cron::set(alice), txflags(tfClose), ter(temINVALID_FLAG));
env(cron::set(alice),
txflags(tfUniversalMask),
ter(temINVALID_FLAG));
}
// temMALFORMED
{
// Invalid DelaySeconds and RepeatCount combination
// (only RepeatCount specified)
env(cron::set(alice), cron::repeat(256), ter(temMALFORMED));
env.close();
// Invalid DelaySeconds
env(cron::set(alice),
cron::delay(365 * 24 * 60 * 60 + 1),
cron::repeat(256),
ter(temMALFORMED));
env.close();
// Invalid RepeatCount
env(cron::set(alice),
cron::delay(365 * 24 * 60 * 60),
cron::repeat(257),
ter(temMALFORMED));
env.close();
}
}
void
testInvalidPreclaim(FeatureBitset features)
{
testcase("invalid preclaim");
using namespace test::jtx;
using namespace std::literals;
// no preclaim checks exists
BEAST_EXPECT(true);
}
void
testDoApply(FeatureBitset features)
{
testcase("doApply");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account("alice");
Env env{*this, features | featureCron};
env.fund(XRP(1000), alice);
env.close();
auto const aliceOwnerCount = ownerCount(env, alice);
// create cron
env(cron::set(alice),
cron::delay(356 * 24 * 60 * 60),
cron::repeat(256),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// increment owner count
BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount + 1);
auto const accSle = env.le(keylet::account(alice.id()));
BEAST_EXPECT(accSle);
BEAST_EXPECT(accSle->isFieldPresent(sfCron));
auto const cronKey = keylet::child(accSle->getFieldH256(sfCron));
auto const cronSle = env.le(cronKey);
BEAST_EXPECT(cronSle);
BEAST_EXPECT(
cronSle->getFieldU32(sfDelaySeconds) == 356 * 24 * 60 * 60);
BEAST_EXPECT(cronSle->getFieldU32(sfRepeatCount) == 256);
// update cron
env(cron::set(alice),
cron::delay(100),
cron::repeat(10),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// owner count does not change
BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount + 1);
auto const accSle2 = env.le(keylet::account(alice.id()));
BEAST_EXPECT(accSle2);
BEAST_EXPECT(accSle2->isFieldPresent(sfCron));
// old cron sle is deleted
BEAST_EXPECT(!env.le(cronKey));
auto const cronKey2 = keylet::child(accSle2->getFieldH256(sfCron));
auto const cronSle2 = env.le(cronKey2);
BEAST_EXPECT(cronSle2);
BEAST_EXPECT(cronSle2->getFieldU32(sfDelaySeconds) == 100);
BEAST_EXPECT(cronSle2->getFieldU32(sfRepeatCount) == 10);
// delete cron
env(cron::set(alice), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// owner count decremented
BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount);
auto const accSle3 = env.le(keylet::account(alice.id()));
BEAST_EXPECT(accSle3);
BEAST_EXPECT(!accSle3->isFieldPresent(sfCron));
// old cron sle is deleted
BEAST_EXPECT(!env.le(cronKey2));
}
void
testWithFeats(FeatureBitset features)
{
testEnabled(features);
testFee(features);
testInvalidPreflight(features);
testInvalidPreclaim(features);
testDoApply(features);
}
public:
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeats(sa);
}
};
BEAST_DEFINE_TESTSUITE(Cron, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -33,6 +33,7 @@
#include <test/jtx/amount.h> #include <test/jtx/amount.h>
#include <test/jtx/balance.h> #include <test/jtx/balance.h>
#include <test/jtx/check.h> #include <test/jtx/check.h>
#include <test/jtx/cron.h>
#include <test/jtx/delivermin.h> #include <test/jtx/delivermin.h>
#include <test/jtx/deposit.h> #include <test/jtx/deposit.h>
#include <test/jtx/escrow.h> #include <test/jtx/escrow.h>

74
src/test/jtx/cron.h Normal file
View File

@@ -0,0 +1,74 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 XRPL Labs
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.
*/
//==============================================================================
#ifndef RIPPLE_TEST_JTX_CRON_H_INCLUDED
#define RIPPLE_TEST_JTX_CRON_H_INCLUDED
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
namespace ripple {
namespace test {
namespace jtx {
/** Cron operations. */
namespace cron {
/** Set a cron. */
Json::Value
set(jtx::Account const& account);
/** Sets the optional DelaySeconds on a JTx. */
class delay
{
private:
uint32_t delay_;
public:
explicit delay(uint32_t delay) : delay_(delay)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Sets the optional RepeatCount on a JTx. */
class repeat
{
private:
uint32_t repeat_;
public:
explicit repeat(uint32_t repeat) : repeat_(repeat)
{
}
void
operator()(Env&, JTx& jtx) const;
};
} // namespace cron
} // namespace jtx
} // namespace test
} // namespace ripple
#endif // RIPPLE_TEST_JTX_CRON_H_INCLUDED

View File

@@ -0,0 +1,56 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 XRPL Labs
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 <ripple/protocol/jss.h>
#include <test/jtx/cron.h>
namespace ripple {
namespace test {
namespace jtx {
namespace cron {
// Set a cron.
Json::Value
set(jtx::Account const& account)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::SetCron;
jv[jss::Account] = account.human();
return jv;
}
void
delay::operator()(Env& env, JTx& jt) const
{
jt.jv[sfDelaySeconds.jsonName] = delay_;
}
void
repeat::operator()(Env& env, JTx& jt) const
{
jt.jv[sfRepeatCount.jsonName] = repeat_;
}
} // namespace cron
} // namespace jtx
} // namespace test
} // namespace ripple