mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-03 00:36:37 +00:00
Merge remote-tracking branch 'origin/dev' into feature-export-rng
# Conflicts: # hook/sfcodes.h # include/xrpl/protocol/Feature.h # include/xrpl/protocol/detail/sfields.macro
This commit is contained in:
@@ -194,6 +194,7 @@
|
||||
#define sfSignatureReward ((6U << 16U) + 29U)
|
||||
#define sfMinAccountCreateAmount ((6U << 16U) + 30U)
|
||||
#define sfLPTokenBalance ((6U << 16U) + 31U)
|
||||
#define sfTrustLineRewardAccumulator ((6U << 16U) + 99U)
|
||||
#define sfPublicKey ((7U << 16U) + 1U)
|
||||
#define sfMessageKey ((7U << 16U) + 2U)
|
||||
#define sfSigningPubKey ((7U << 16U) + 3U)
|
||||
@@ -260,6 +261,7 @@
|
||||
#define sfIssuingChainIssue ((24U << 16U) + 2U)
|
||||
#define sfAsset ((24U << 16U) + 3U)
|
||||
#define sfAsset2 ((24U << 16U) + 4U)
|
||||
#define sfClaimCurrency ((24U << 16U) + 5U)
|
||||
#define sfXChainBridge ((25U << 16U) + 1U)
|
||||
#define sfTransactionMetaData ((14U << 16U) + 2U)
|
||||
#define sfCreatedNode ((14U << 16U) + 3U)
|
||||
@@ -298,7 +300,9 @@
|
||||
#define sfActiveValidator ((14U << 16U) + 95U)
|
||||
#define sfGenesisMint ((14U << 16U) + 96U)
|
||||
#define sfRemark ((14U << 16U) + 97U)
|
||||
#define sfExportResult ((14U << 16U) + 98U)
|
||||
#define sfHighReward ((14U << 16U) + 98U)
|
||||
#define sfLowReward ((14U << 16U) + 99U)
|
||||
#define sfExportResult ((14U << 16U) + 100U)
|
||||
#define sfSigners ((15U << 16U) + 3U)
|
||||
#define sfSignerEntries ((15U << 16U) + 4U)
|
||||
#define sfTemplate ((15U << 16U) + 5U)
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 117;
|
||||
static constexpr std::size_t numFeatures = 118;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(IOURewardClaim, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (IOULockedBalanceInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ImportIssuer, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -410,6 +410,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
|
||||
{sfHighQualityOut, soeOPTIONAL},
|
||||
{sfLockedBalance, soeOPTIONAL},
|
||||
{sfLockCount, soeOPTIONAL},
|
||||
{sfHighReward, soeOPTIONAL},
|
||||
{sfLowReward, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** The ledger object which lists the network's fee settings.
|
||||
|
||||
@@ -262,6 +262,7 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfTrustLineRewardAccumulator,AMOUNT, 99)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
@@ -345,6 +346,7 @@ TYPED_SFIELD(sfLockingChainIssue, ISSUE, 1)
|
||||
TYPED_SFIELD(sfIssuingChainIssue, ISSUE, 2)
|
||||
TYPED_SFIELD(sfAsset, ISSUE, 3)
|
||||
TYPED_SFIELD(sfAsset2, ISSUE, 4)
|
||||
TYPED_SFIELD(sfClaimCurrency, ISSUE, 5)
|
||||
|
||||
// bridge
|
||||
TYPED_SFIELD(sfXChainBridge, XCHAIN_BRIDGE, 1)
|
||||
@@ -392,7 +394,9 @@ UNTYPED_SFIELD(sfImportVLKey, OBJECT, 94)
|
||||
UNTYPED_SFIELD(sfActiveValidator, OBJECT, 95)
|
||||
UNTYPED_SFIELD(sfGenesisMint, OBJECT, 96)
|
||||
UNTYPED_SFIELD(sfRemark, OBJECT, 97)
|
||||
UNTYPED_SFIELD(sfExportResult, OBJECT, 98)
|
||||
UNTYPED_SFIELD(sfHighReward, OBJECT, 98)
|
||||
UNTYPED_SFIELD(sfLowReward, OBJECT, 99)
|
||||
UNTYPED_SFIELD(sfExportResult, OBJECT, 100)
|
||||
|
||||
// array of objects (common)
|
||||
// ARRAY/1 is reserved for end of array
|
||||
|
||||
@@ -561,6 +561,7 @@ TRANSACTION(ttIMPORT, 97, Import, ({
|
||||
* from a specified hook */
|
||||
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
{sfClaimCurrency, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
|
||||
|
||||
@@ -254,6 +254,24 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfHighReward.jsonName,
|
||||
sfHighReward.getCode(),
|
||||
{
|
||||
{sfRewardLgrFirst, soeREQUIRED},
|
||||
{sfRewardLgrLast, soeREQUIRED},
|
||||
{sfRewardTime, soeREQUIRED},
|
||||
{sfTrustLineRewardAccumulator, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfLowReward.jsonName,
|
||||
sfLowReward.getCode(),
|
||||
{
|
||||
{sfRewardLgrFirst, soeREQUIRED},
|
||||
{sfRewardLgrLast, soeREQUIRED},
|
||||
{sfRewardTime, soeREQUIRED},
|
||||
{sfTrustLineRewardAccumulator, soeREQUIRED},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -27,6 +27,14 @@ namespace ripple {
|
||||
namespace test {
|
||||
struct ClaimReward_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
// helper
|
||||
void static overrideFlag(Json::Value& jv)
|
||||
{
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
}
|
||||
|
||||
public:
|
||||
bool
|
||||
expectRewards(
|
||||
jtx::Env const& env,
|
||||
@@ -60,6 +68,52 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectRewardsIOU(
|
||||
jtx::Env const& env,
|
||||
jtx::Account const& acct,
|
||||
jtx::IOU const& iou,
|
||||
std::uint32_t ledgerFirst,
|
||||
std::uint32_t ledgerLast,
|
||||
STAmount accumulator,
|
||||
std::uint32_t time)
|
||||
{
|
||||
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
|
||||
BEAST_EXPECT(!!sle);
|
||||
auto const& sfRewardField =
|
||||
std::minmax(acct.id(), iou.account.id()).first == acct.id()
|
||||
? sfLowReward
|
||||
: sfHighReward;
|
||||
|
||||
if (!sle->isFieldPresent(sfRewardField))
|
||||
return false;
|
||||
|
||||
auto const& reward =
|
||||
static_cast<STObject const&>(sle->peekAtField(sfRewardField));
|
||||
|
||||
if (!reward.isFieldPresent(sfRewardLgrFirst) ||
|
||||
reward.getFieldU32(sfRewardLgrFirst) != ledgerFirst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfRewardLgrLast) ||
|
||||
reward.getFieldU32(sfRewardLgrLast) != ledgerLast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfTrustLineRewardAccumulator) ||
|
||||
reward.getFieldAmount(sfTrustLineRewardAccumulator) != accumulator)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfRewardTime) ||
|
||||
reward.getFieldU32(sfRewardTime) != time)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectNoRewards(jtx::Env const& env, jtx::Account const& acct)
|
||||
{
|
||||
@@ -83,6 +137,24 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectNoRewardsIOU(
|
||||
jtx::Env const& env,
|
||||
jtx::Account const& acct,
|
||||
jtx::IOU const& iou)
|
||||
{
|
||||
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
|
||||
BEAST_EXPECT(!!sle);
|
||||
auto const& sfRewardField =
|
||||
std::minmax(acct.id(), iou.account.id()).first == acct.id()
|
||||
? sfLowReward
|
||||
: sfHighReward;
|
||||
|
||||
if (sle->isFieldPresent(sfRewardField))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled(FeatureBitset features)
|
||||
{
|
||||
@@ -92,7 +164,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
|
||||
// setup env
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
for (bool const withClaimReward : {false, true})
|
||||
{
|
||||
@@ -102,7 +174,11 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
withClaimReward ? features : features - featureBalanceRewards;
|
||||
Env env{*this, amend};
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const txResult =
|
||||
@@ -119,7 +195,10 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
.count();
|
||||
|
||||
// CLAIM
|
||||
env(reward::claim(alice), reward::issuer(issuer), txResult);
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
txResult);
|
||||
env.close();
|
||||
|
||||
if (withClaimReward)
|
||||
@@ -195,14 +274,19 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
Env env{*this, amend};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
txflags(tfFullyCanonicalSig),
|
||||
withFixFlags ? ter(tesSUCCESS) : ter(temINVALID_FLAG));
|
||||
env.close();
|
||||
@@ -221,6 +305,105 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
env(reward::claim(alice), reward::issuer(alice), ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// featureIOURewardClaim
|
||||
|
||||
// temDISABLED
|
||||
// featureIOURewardClaim amendment is disabled
|
||||
{
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337),
|
||||
features - featureIOURewardClaim};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, bob, gw);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(bob),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// temMALFORMED
|
||||
// ClaimCurrency.account cannot be the source account.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = alice["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(bob),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// temMALFORMED
|
||||
// Issuer cannot be Genesis account if ClaimCurrency is set.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, gw);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(Account::master),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temBAD_ISSUER));
|
||||
}
|
||||
|
||||
// MPT
|
||||
{
|
||||
// tested in testMPTInvalidInTx() at MPToken_test.cpp
|
||||
}
|
||||
|
||||
// XAH RewardClaim: Issuer must be the Genesis account if
|
||||
// featureXahauGenesis and featureIOURewardClaim are enabled.
|
||||
for (bool const withIOURewardClaim : {false, true})
|
||||
{
|
||||
auto const amend = withIOURewardClaim
|
||||
? features
|
||||
: features - featureIOURewardClaim;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const badIssuer = Account("gw");
|
||||
auto const issuer = Account::master;
|
||||
auto const USD = badIssuer["USD"];
|
||||
|
||||
Env env{*this, amend};
|
||||
env.fund(XRP(1000), alice, badIssuer);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(badIssuer),
|
||||
fee(XRP(1)),
|
||||
withIOURewardClaim ? ter(temBAD_ISSUER) : ter(tesSUCCESS));
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -244,6 +427,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.memoize(alice);
|
||||
auto USD = issuer["USD"];
|
||||
|
||||
env.fund(XRP(1000), issuer);
|
||||
env.close();
|
||||
@@ -251,7 +435,10 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
auto tx = reward::claim(alice);
|
||||
tx[jss::Sequence] = 0;
|
||||
tx[jss::Fee] = 10;
|
||||
env(tx, reward::issuer(issuer), ter(terNO_ACCOUNT));
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(USD),
|
||||
ter(terNO_ACCOUNT));
|
||||
env.close();
|
||||
}
|
||||
|
||||
@@ -261,9 +448,9 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
@@ -294,11 +481,16 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
auto const issuer = Account("issuer");
|
||||
env.memoize(issuer);
|
||||
|
||||
auto USD = issuer["USD"];
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx, reward::issuer(issuer), ter(tecNO_ISSUER));
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(USD),
|
||||
ter(tecNO_ISSUER));
|
||||
env.close();
|
||||
}
|
||||
|
||||
@@ -320,9 +512,162 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(amm.ammAccount()),
|
||||
reward::claimCurrency(USD),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// tecNO_TARGET
|
||||
// no claim reward hook
|
||||
{
|
||||
Env env{*this};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
// Doesn't have hook
|
||||
{
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// Invalid HookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// Invalid IncomingHookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj.removeMember(jss::HookOn);
|
||||
hookObj[jss::HookOnIncoming] =
|
||||
to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
}
|
||||
// Vaild HookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
// Vaild IncomingHookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj.removeMember(jss::HookOn);
|
||||
hookObj[jss::HookOnIncoming] =
|
||||
to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
// Invalid Hooks Array
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(
|
||||
issuer,
|
||||
{{
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
}},
|
||||
0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_TARGET));
|
||||
}
|
||||
// Vaild Hooks Array
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
auto hookObj2 = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj2[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(
|
||||
issuer,
|
||||
{{
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj2,
|
||||
}},
|
||||
0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
// tecNO_LINE
|
||||
// trustline does not exist.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, gw);
|
||||
env.close();
|
||||
|
||||
env(hook(gw, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(USD),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_LINE));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -335,9 +680,17 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice, bob, gw, issuer);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
env(hook(Account::master, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// test claim rewards - no opt out
|
||||
@@ -352,7 +705,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
.count();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx, reward::issuer(issuer), ter(tesSUCCESS));
|
||||
env(tx, reward::issuer(Account::master), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
@@ -365,6 +718,51 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(expectNoRewards(env, alice) == true);
|
||||
|
||||
// test iou claim rewards
|
||||
{
|
||||
// set trustline
|
||||
env(trust(bob, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
// opt in
|
||||
auto const currentLedger = env.current()->seq();
|
||||
auto const currentTime =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(
|
||||
env.app()
|
||||
.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count();
|
||||
|
||||
auto tx = reward::claim(bob);
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
bob,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger,
|
||||
gw["USD"](0),
|
||||
currentTime) == true);
|
||||
|
||||
// opt out
|
||||
env(reward::claim(bob),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
txflags(tfOptOut),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(expectNoRewardsIOU(env, bob, gw["USD"]) == true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -375,16 +773,20 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(10000), alice, issuer);
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(10000), alice);
|
||||
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
|
||||
env(ticket::create(alice, 10));
|
||||
std::uint32_t const aliceSeq{env.seq(alice)};
|
||||
env.require(owners(alice, 10));
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ticket::use(aliceTicketSeq++),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
|
||||
@@ -392,6 +794,317 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
env.require(owners(alice, 9));
|
||||
}
|
||||
|
||||
void
|
||||
testBalanceChanges(FeatureBitset features)
|
||||
{
|
||||
testcase("balance changes");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
auto const getCurrentTime = [&](Env& env) {
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||
env.app()
|
||||
.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count();
|
||||
};
|
||||
|
||||
// Native Reward Claim
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const gw = Account("gw");
|
||||
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(10001), alice, gw);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const currentTime = getCurrentTime(env);
|
||||
auto const currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(alice), reward::issuer(issuer), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(fset(alice, 0));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewards(
|
||||
env,
|
||||
alice,
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
10000, // 10000 XAH * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// IOU Reward Claim
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
user["USD"](10000), // 10000 USD * time 1
|
||||
currentTime) == true);
|
||||
|
||||
env(pay(gw, user, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
// check Balance == 0
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// Check Balance minus -> plus, plus -> minus
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)));
|
||||
env.close();
|
||||
env(trust(gw, user["USD"](1000000)));
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](20000)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// test with escrow (locked balance)
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
for (bool const hasEscrow : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
if (hasEscrow)
|
||||
{
|
||||
env(escrow(user, user, gw["USD"](2000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(user, gw, gw["USD"](5000)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
user["USD"](10000), // 10000 USD * time 1
|
||||
currentTime) == true);
|
||||
|
||||
env(pay(gw, user, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
// check Balance == 0
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](
|
||||
15000), // 10000 USD * time 1 + 5000 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
}
|
||||
|
||||
// STAmount overflow in reward accumulation should not cause
|
||||
// transaction failure (tefEXCEPTION). The overflow should be
|
||||
// gracefully skipped via try-catch.
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// Use a near-max IOU balance at exponent 80. When
|
||||
// multiply(balance, STAmount(lgrElapsed), issue) is called
|
||||
// with lgrElapsed >= 2, the result exponent exceeds
|
||||
// cMaxOffset(80), causing IOUAmount::normalize to throw
|
||||
// std::overflow_error("value overflow").
|
||||
auto const bigUSD = STAmount{
|
||||
gw["USD"].issue(), std::uint64_t(5000000000000000ull), 80};
|
||||
// Payment amount must be large enough to register a
|
||||
// balance change given STAmount's 16-digit precision.
|
||||
auto const payBackUSD = STAmount{
|
||||
gw["USD"].issue(), std::uint64_t(1000000000000000ull), 80};
|
||||
|
||||
env(trust(user, bigUSD));
|
||||
env.close();
|
||||
env(pay(gw, user, bigUSD));
|
||||
env.close();
|
||||
|
||||
// Claim IOU reward to initialize reward tracking
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// Advance ledger so lgrElapsed >= 2. With lgrElapsed=1
|
||||
// the multiply result is exactly at cMaxOffset boundary
|
||||
// (no overflow). With lgrElapsed >= 2, the result exponent
|
||||
// exceeds cMaxOffset and triggers the overflow.
|
||||
env.close();
|
||||
|
||||
// This payment modifies the trustline balance, triggering
|
||||
// reward accumulation in Transactor. Without the try-catch
|
||||
// fix, multiply() throws std::overflow_error("value overflow")
|
||||
// and the transaction fails with tefEXCEPTION.
|
||||
env(pay(user, gw, payBackUSD), ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -400,6 +1113,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
testInvalidPreclaim(features);
|
||||
testValidNoHook(features);
|
||||
testUsingTickets(features);
|
||||
testBalanceChanges(features);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -1878,6 +1878,13 @@ class MPToken_test : public beast::unit_test::suite
|
||||
[jss::Amount] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, sfAmounts.jsonName.c_str());
|
||||
}
|
||||
// ClaimReward
|
||||
{
|
||||
Json::Value jv = reward::claim(alice);
|
||||
jv[sfClaimCurrency.jsonName][jss::mpt_issuance_id] =
|
||||
to_string(issue);
|
||||
test(jv, sfClaimCurrency.jsonName.c_str());
|
||||
}
|
||||
}
|
||||
for (const auto& str : txWithAmounts)
|
||||
printf("%s\n", str.c_str());
|
||||
|
||||
@@ -1647,8 +1647,12 @@ private:
|
||||
features};
|
||||
|
||||
auto const account = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(1000), account, issuer);
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(1000), account);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// set tsh collect
|
||||
@@ -1680,8 +1684,8 @@ private:
|
||||
features};
|
||||
|
||||
auto const account = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(1000), account, issuer);
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(1000), account);
|
||||
env.close();
|
||||
|
||||
// set tsh collect
|
||||
|
||||
@@ -207,19 +207,25 @@ private:
|
||||
test::jtx::Env env{*this, envconfig(), features};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// claim reward
|
||||
env(reward::claim(alice), reward::issuer(issuer), ter(tesSUCCESS));
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify touch
|
||||
validateTouch(env, alice, {"ClaimReward", "tesSUCCESS"});
|
||||
auto const tt = env.current()->rules().enabled(featureTouch)
|
||||
? "ClaimReward"
|
||||
: "AccountSet";
|
||||
: "SetHook";
|
||||
validateTouch(env, issuer, {tt, "tesSUCCESS"});
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ issuer::operator()(Env& env, JTx& jt) const
|
||||
jt.jv[sfIssuer.jsonName] = to_string(issuer_);
|
||||
}
|
||||
|
||||
void
|
||||
claimCurrency::operator()(Env& env, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfClaimCurrency.jsonName] =
|
||||
STIssue{sfClaimCurrency, claimCurrency_}.getJson(JsonOptions::none);
|
||||
}
|
||||
|
||||
} // namespace reward
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
@@ -56,6 +56,21 @@ public:
|
||||
operator()(Env&, JTx& jtx) const;
|
||||
};
|
||||
|
||||
/** Sets the optional ClaimCurrency on a JTx. */
|
||||
class claimCurrency
|
||||
{
|
||||
private:
|
||||
Issue claimCurrency_;
|
||||
|
||||
public:
|
||||
explicit claimCurrency(Issue const& issue) : claimCurrency_(issue)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jtx) const;
|
||||
};
|
||||
|
||||
} // namespace reward
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/tx/detail/ClaimReward.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
@@ -60,6 +61,57 @@ ClaimReward::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfClaimCurrency))
|
||||
{
|
||||
// IOU RewardClaim
|
||||
if (!ctx.rules.enabled(featureIOURewardClaim))
|
||||
return temDISABLED;
|
||||
|
||||
auto const claimAsset = ctx.tx[sfClaimCurrency];
|
||||
bool const isMPT = claimAsset.holds<MPTIssue>();
|
||||
|
||||
if (isMPT)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "ClaimReward: MPT is not supported yet.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const claimIssue = claimAsset.get<Issue>();
|
||||
if (claimIssue.account == beast::zero || isXRP(claimIssue.currency))
|
||||
return temMALFORMED;
|
||||
|
||||
if (claimIssue.account == ctx.tx.getAccountID(sfAccount))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.rules.enabled(featureXahauGenesis) &&
|
||||
ctx.rules.enabled(featureIOURewardClaim) &&
|
||||
ctx.tx.isFieldPresent(sfIssuer))
|
||||
{
|
||||
static auto const genesisAccountId = calcAccountID(
|
||||
generateKeyPair(
|
||||
KeyType::secp256k1, generateSeed("masterpassphrase"))
|
||||
.first);
|
||||
auto const issuer = ctx.tx.getAccountID(sfIssuer);
|
||||
if (ctx.tx.isFieldPresent(sfClaimCurrency))
|
||||
{
|
||||
if (issuer == genesisAccountId)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "ClaimReward (IOU): Issuer cannot "
|
||||
"be the Genesis account";
|
||||
return temBAD_ISSUER;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (issuer != genesisAccountId)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "ClaimReward (XAH): Issuer must be "
|
||||
"the Genesis account";
|
||||
return temBAD_ISSUER;
|
||||
}
|
||||
}
|
||||
}
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
@@ -90,8 +142,53 @@ ClaimReward::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (sleIssuer->isFieldPresent(sfAMMID))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx.view.rules().enabled(featureIOURewardClaim))
|
||||
{
|
||||
auto const& sleHook = ctx.view.read(keylet::hook(*issuer));
|
||||
if (!sleHook || !sleHook->isFieldPresent(sfHooks) ||
|
||||
sleHook->getFieldArray(sfHooks).empty())
|
||||
return tecNO_TARGET;
|
||||
|
||||
bool hasClaimRewardHook = false;
|
||||
auto const& hooks = sleHook->getFieldArray(sfHooks);
|
||||
for (auto const& hook : hooks)
|
||||
{
|
||||
if (!hook.isFieldPresent(sfHookHash))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
auto const& hash = hook.getFieldH256(sfHookHash);
|
||||
auto const& sleDef =
|
||||
ctx.view.read(keylet::hookDefinition(hash));
|
||||
if (!sleDef)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& hookOn =
|
||||
hook::getHookOn(hook, sleDef, sfHookOnIncoming);
|
||||
if (hook::canHook(ttCLAIM_REWARD, hookOn))
|
||||
{
|
||||
hasClaimRewardHook = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasClaimRewardHook)
|
||||
return tecNO_TARGET;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfClaimCurrency))
|
||||
{
|
||||
auto const claimCurrency = ctx.tx[sfClaimCurrency];
|
||||
bool const isMPT = claimCurrency.holds<MPTIssue>();
|
||||
|
||||
if (isMPT)
|
||||
return tefINTERNAL;
|
||||
|
||||
auto const claimIssue = claimCurrency.get<Issue>();
|
||||
|
||||
if (!ctx.view.exists(
|
||||
keylet::line(id, claimIssue.account, claimIssue.currency)))
|
||||
return tecNO_LINE;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -105,6 +202,61 @@ ClaimReward::doApply()
|
||||
std::optional<uint32_t> flags = ctx_.tx[~sfFlags];
|
||||
|
||||
bool isOptOut = flags && *flags == tfOptOut;
|
||||
|
||||
uint32_t lgrCur = view().seq();
|
||||
uint32_t lgrFirst = lgrCur;
|
||||
uint32_t lgrLast = lgrCur;
|
||||
uint64_t accumulator = 0ULL;
|
||||
uint32_t rewardTime = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
ctx_.app.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count();
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfClaimCurrency))
|
||||
{
|
||||
auto const claimCurrency = ctx_.tx[sfClaimCurrency];
|
||||
bool const isMPT = claimCurrency.holds<MPTIssue>();
|
||||
|
||||
if (isMPT)
|
||||
return tefINTERNAL;
|
||||
|
||||
auto const claimIssue = claimCurrency.get<Issue>();
|
||||
|
||||
auto lineSle = view().peek(
|
||||
keylet::line(account_, claimIssue.account, claimIssue.currency));
|
||||
if (!lineSle)
|
||||
return tefINTERNAL;
|
||||
|
||||
bool const isHigh = account_ > claimIssue.account;
|
||||
auto const& rewardField = isHigh ? sfHighReward : sfLowReward;
|
||||
|
||||
if (isOptOut)
|
||||
{
|
||||
if (lineSle->isFieldPresent(rewardField))
|
||||
lineSle->makeFieldAbsent(rewardField);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& rewardAccumulatorField =
|
||||
lineSle->getFieldAmount(isHigh ? sfHighLimit : sfLowLimit)
|
||||
.zeroed();
|
||||
|
||||
// all actual rewards are handled by the hook on the sfIssuer
|
||||
// the tt just resets the counters
|
||||
auto& reward = lineSle->peekFieldObject(rewardField);
|
||||
reward.setFieldU32(sfRewardLgrFirst, lgrFirst);
|
||||
reward.setFieldU32(sfRewardLgrLast, lgrLast);
|
||||
reward.setFieldAmount(
|
||||
sfTrustLineRewardAccumulator, rewardAccumulatorField);
|
||||
reward.setFieldU32(sfRewardTime, rewardTime);
|
||||
}
|
||||
|
||||
view().update(lineSle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (isOptOut)
|
||||
{
|
||||
if (sle->isFieldPresent(sfRewardLgrFirst))
|
||||
@@ -120,18 +272,10 @@ ClaimReward::doApply()
|
||||
{
|
||||
// all actual rewards are handled by the hook on the sfIssuer
|
||||
// the tt just resets the counters
|
||||
uint32_t lgrCur = view().seq();
|
||||
sle->setFieldU32(sfRewardLgrFirst, lgrCur);
|
||||
sle->setFieldU32(sfRewardLgrLast, lgrCur);
|
||||
sle->setFieldU64(sfRewardAccumulator, 0ULL);
|
||||
sle->setFieldU32(
|
||||
sfRewardTime,
|
||||
std::chrono::duration_cast<std::chrono::seconds>(
|
||||
ctx_.app.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count());
|
||||
sle->setFieldU32(sfRewardLgrFirst, lgrFirst);
|
||||
sle->setFieldU32(sfRewardLgrLast, lgrLast);
|
||||
sle->setFieldU64(sfRewardAccumulator, accumulator);
|
||||
sle->setFieldU32(sfRewardTime, rewardTime);
|
||||
}
|
||||
|
||||
view().update(sle);
|
||||
|
||||
@@ -2152,7 +2152,8 @@ Transactor::operator()()
|
||||
|
||||
bool const has240819 = view().rules().enabled(fix240819);
|
||||
bool const has240911 = view().rules().enabled(fix240911);
|
||||
|
||||
bool const hasIOURewardClaim =
|
||||
view().rules().enabled(featureIOURewardClaim);
|
||||
auto const& sfRewardFields =
|
||||
*(ripple::SField::knownCodeToField.at(917511 - has240819));
|
||||
|
||||
@@ -2162,11 +2163,92 @@ Transactor::operator()()
|
||||
SField const& metaType = node.getFName();
|
||||
uint16_t nodeType = node.getFieldU16(sfLedgerEntryType);
|
||||
|
||||
// we only care about ltACCOUNT_ROOT objects being modified or
|
||||
// created
|
||||
if (nodeType != ltACCOUNT_ROOT || metaType == sfDeletedNode)
|
||||
// we only care about ltACCOUNT_ROOT and ltRIPPLE_STATE objects
|
||||
// being modified or created
|
||||
if ((nodeType != ltACCOUNT_ROOT && nodeType != ltRIPPLE_STATE) ||
|
||||
metaType == sfDeletedNode)
|
||||
continue;
|
||||
|
||||
// ltRippleState
|
||||
if (nodeType == ltRIPPLE_STATE)
|
||||
{
|
||||
if (!hasIOURewardClaim)
|
||||
continue;
|
||||
|
||||
if (!node.isFieldPresent(sfPreviousFields) ||
|
||||
!node.isFieldPresent(sfLedgerIndex))
|
||||
continue;
|
||||
auto sle = view().peek(
|
||||
Keylet{ltRIPPLE_STATE, node.getFieldH256(sfLedgerIndex)});
|
||||
if (!sle)
|
||||
continue;
|
||||
STObject& previousFields = (const_cast<STObject&>(node))
|
||||
.getField(sfPreviousFields)
|
||||
.downcast<STObject>();
|
||||
if (!previousFields.isFieldPresent(sfBalance))
|
||||
continue;
|
||||
|
||||
auto balance = previousFields.getFieldAmount(sfBalance);
|
||||
|
||||
if (balance.native())
|
||||
continue;
|
||||
|
||||
SField const* sfRewardFields[] = {&sfLowReward, &sfHighReward};
|
||||
for (auto const* sfRewardFieldPtr : sfRewardFields)
|
||||
{
|
||||
auto const& sfRewardField = *sfRewardFieldPtr;
|
||||
|
||||
if (!sle->isFieldPresent(sfRewardField))
|
||||
continue;
|
||||
|
||||
auto balance_ = balance;
|
||||
if (sfRewardField == sfHighReward)
|
||||
balance_.negate();
|
||||
|
||||
if (balance_.negative())
|
||||
balance_.clear();
|
||||
|
||||
auto& reward = sle->peekFieldObject(sfRewardField);
|
||||
uint32_t lgrLast = reward.getFieldU32(sfRewardLgrLast);
|
||||
uint32_t lgrElapsed = lgrCur - lgrLast;
|
||||
|
||||
// update even in cases such as overflow or underflow.
|
||||
reward.setFieldU32(sfRewardLgrLast, lgrCur);
|
||||
|
||||
// overflow safety
|
||||
if (lgrElapsed > lgrCur || lgrElapsed == 0)
|
||||
continue;
|
||||
|
||||
auto accum =
|
||||
reward.getFieldAmount(sfTrustLineRewardAccumulator);
|
||||
|
||||
STAmount accumNew;
|
||||
try
|
||||
{
|
||||
accumNew = accum +
|
||||
multiply(balance_,
|
||||
STAmount(((uint64_t)lgrElapsed)),
|
||||
balance_.issue());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
// Overflow detected, skip this reward calculation
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for overflow(<) and underflow(=)
|
||||
if (accumNew <= accum)
|
||||
continue;
|
||||
|
||||
reward.setFieldAmount(
|
||||
sfTrustLineRewardAccumulator, accumNew);
|
||||
}
|
||||
|
||||
view().update(sle);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ltAccountRoot
|
||||
if (!node.isFieldPresent(sfRewardFields) ||
|
||||
!node.isFieldPresent(sfLedgerIndex))
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user