mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-04 10:45:50 +00:00
ExtendedHookState (#406)
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#define sfHookEmitCount ((1U << 16U) + 18U)
|
||||
#define sfHookExecutionIndex ((1U << 16U) + 19U)
|
||||
#define sfHookApiVersion ((1U << 16U) + 20U)
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
#define sfNetworkID ((2U << 16U) + 1U)
|
||||
#define sfFlags ((2U << 16U) + 2U)
|
||||
#define sfSourceTag ((2U << 16U) + 3U)
|
||||
|
||||
@@ -29,6 +29,8 @@ enum HookEmissionFlags : uint16_t {
|
||||
};
|
||||
} // namespace ripple
|
||||
|
||||
using namespace ripple;
|
||||
|
||||
namespace hook {
|
||||
// RH TODO: put these somewhere better, and allow rules to be fed in
|
||||
inline uint32_t
|
||||
@@ -43,11 +45,27 @@ maxHookParameterValueSize(void)
|
||||
return 256;
|
||||
}
|
||||
|
||||
inline uint32_t
|
||||
maxHookStateDataSize(void)
|
||||
inline uint16_t
|
||||
maxHookStateScale(void)
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
inline uint32_t
|
||||
maxHookStateDataSize(uint16_t hookStateScale)
|
||||
{
|
||||
if (hookStateScale == 0)
|
||||
{
|
||||
// should not happen, but just in case
|
||||
return 256U;
|
||||
}
|
||||
if (hookStateScale > maxHookStateScale())
|
||||
{
|
||||
// should not happen, but just in case
|
||||
return 256 * maxHookStateScale();
|
||||
}
|
||||
return 256U * hookStateScale;
|
||||
}
|
||||
|
||||
inline uint32_t
|
||||
maxHookWasmSize(void)
|
||||
|
||||
@@ -32,6 +32,7 @@ class HookStateMap : public std::map<
|
||||
std::tuple<
|
||||
int64_t, // remaining available ownercount
|
||||
int64_t, // total namespace count
|
||||
uint16_t, // hook state scale
|
||||
std::map<
|
||||
ripple::uint256, // namespace
|
||||
std::map<
|
||||
@@ -465,9 +466,6 @@ apply(
|
||||
|
||||
struct HookContext;
|
||||
|
||||
uint32_t
|
||||
computeHookStateOwnerCount(uint32_t hookStateCount);
|
||||
|
||||
int64_t
|
||||
computeExecutionFee(uint64_t instructionCount);
|
||||
int64_t
|
||||
|
||||
@@ -862,12 +862,6 @@ parseCurrency(uint8_t* cu_ptr, uint32_t cu_len)
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t
|
||||
hook::computeHookStateOwnerCount(uint32_t hookStateCount)
|
||||
{
|
||||
return hookStateCount;
|
||||
}
|
||||
|
||||
inline int64_t
|
||||
serialize_keylet(
|
||||
ripple::Keylet& kl,
|
||||
@@ -1080,14 +1074,18 @@ hook::setHookState(
|
||||
return tefINTERNAL;
|
||||
|
||||
// if the blob is too large don't set it
|
||||
if (data.size() > hook::maxHookStateDataSize())
|
||||
uint16_t const hookStateScale = sleAccount->isFieldPresent(sfHookStateScale)
|
||||
? sleAccount->getFieldU16(sfHookStateScale)
|
||||
: 1;
|
||||
|
||||
if (data.size() > hook::maxHookStateDataSize(hookStateScale))
|
||||
return temHOOK_DATA_TOO_LARGE;
|
||||
|
||||
auto hookStateKeylet = ripple::keylet::hookState(acc, key, ns);
|
||||
auto hookStateDirKeylet = ripple::keylet::hookStateDir(acc, ns);
|
||||
|
||||
uint32_t stateCount = sleAccount->getFieldU32(sfHookStateCount);
|
||||
uint32_t oldStateReserve = computeHookStateOwnerCount(stateCount);
|
||||
uint32_t oldStateCount = stateCount;
|
||||
|
||||
auto hookState = view.peek(hookStateKeylet);
|
||||
|
||||
@@ -1118,12 +1116,14 @@ hook::setHookState(
|
||||
if (stateCount > 0)
|
||||
--stateCount; // guard this because in the "impossible" event it is
|
||||
// already 0 we'll wrap back to int_max
|
||||
|
||||
// if removing this state entry would destroy the allotment then reduce
|
||||
// the owner count
|
||||
if (computeHookStateOwnerCount(stateCount) < oldStateReserve)
|
||||
adjustOwnerCount(view, sleAccount, -1, j);
|
||||
if (stateCount < oldStateCount)
|
||||
adjustOwnerCount(view, sleAccount, -hookStateScale, j);
|
||||
|
||||
if (view.rules().enabled(featureExtendedHookState) && stateCount == 0)
|
||||
sleAccount->makeFieldAbsent(sfHookStateCount);
|
||||
else
|
||||
sleAccount->setFieldU32(sfHookStateCount, stateCount);
|
||||
|
||||
if (nsDestroyed)
|
||||
@@ -1151,19 +1151,19 @@ hook::setHookState(
|
||||
{
|
||||
++stateCount;
|
||||
|
||||
if (computeHookStateOwnerCount(stateCount) > oldStateReserve)
|
||||
if (stateCount > oldStateCount)
|
||||
{
|
||||
// the hook used its allocated allotment of state entries for its
|
||||
// previous ownercount increment ownercount and give it another
|
||||
// allotment
|
||||
|
||||
++ownerCount;
|
||||
ownerCount += hookStateScale;
|
||||
XRPAmount const newReserve{view.fees().accountReserve(ownerCount)};
|
||||
|
||||
if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
adjustOwnerCount(view, sleAccount, 1, j);
|
||||
adjustOwnerCount(view, sleAccount, hookStateScale, j);
|
||||
}
|
||||
|
||||
// update state count
|
||||
@@ -1451,7 +1451,7 @@ lookup_state_cache(
|
||||
if (stateMap.find(acc) == stateMap.end())
|
||||
return std::nullopt;
|
||||
|
||||
auto& stateMapAcc = std::get<2>(stateMap[acc]);
|
||||
auto& stateMapAcc = std::get<3>(stateMap[acc]);
|
||||
if (stateMapAcc.find(ns) == stateMapAcc.end())
|
||||
return std::nullopt;
|
||||
|
||||
@@ -1486,6 +1486,7 @@ set_state_cache(
|
||||
|
||||
if (stateMap.find(acc) == stateMap.end())
|
||||
{
|
||||
// new Account Key
|
||||
// if this is the first time this account has been interacted with
|
||||
// we will compute how many available reserve positions there are
|
||||
auto const& fees = hookCtx.applyCtx.view().fees();
|
||||
@@ -1497,6 +1498,10 @@ set_state_cache(
|
||||
|
||||
STAmount bal = accSLE->getFieldAmount(sfBalance);
|
||||
|
||||
uint16_t const hookStateScale = accSLE->isFieldPresent(sfHookStateScale)
|
||||
? accSLE->getFieldU16(sfHookStateScale)
|
||||
: 1;
|
||||
|
||||
int64_t availableForReserves = bal.xrp().drops() -
|
||||
fees.accountReserve(accSLE->getFieldU32(sfOwnerCount)).drops();
|
||||
|
||||
@@ -1507,7 +1512,7 @@ set_state_cache(
|
||||
|
||||
availableForReserves /= increment;
|
||||
|
||||
if (availableForReserves < 1 && modified)
|
||||
if (availableForReserves < hookStateScale && modified)
|
||||
return RESERVE_INSUFFICIENT;
|
||||
|
||||
int64_t namespaceCount = accSLE->isFieldPresent(sfHookNamespaces)
|
||||
@@ -1526,20 +1531,28 @@ set_state_cache(
|
||||
|
||||
stateMap.modified_entry_count++;
|
||||
|
||||
// sanity check
|
||||
if (view.rules().enabled(featureExtendedHookState) &&
|
||||
availableForReserves < hookStateScale)
|
||||
return INTERNAL_ERROR;
|
||||
|
||||
stateMap[acc] = {
|
||||
availableForReserves - 1,
|
||||
availableForReserves - hookStateScale,
|
||||
namespaceCount,
|
||||
hookStateScale,
|
||||
{{ns, {{key, {modified, data}}}}}};
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto& availableForReserves = std::get<0>(stateMap[acc]);
|
||||
auto& namespaceCount = std::get<1>(stateMap[acc]);
|
||||
auto& stateMapAcc = std::get<2>(stateMap[acc]);
|
||||
bool const canReserveNew = availableForReserves > 0;
|
||||
auto& hookStateScale = std::get<2>(stateMap[acc]);
|
||||
auto& stateMapAcc = std::get<3>(stateMap[acc]);
|
||||
bool const canReserveNew = availableForReserves >= hookStateScale;
|
||||
|
||||
if (stateMapAcc.find(ns) == stateMapAcc.end())
|
||||
{
|
||||
// new Namespace Key
|
||||
if (modified)
|
||||
{
|
||||
if (!canReserveNew)
|
||||
@@ -1557,7 +1570,11 @@ set_state_cache(
|
||||
namespaceCount++;
|
||||
}
|
||||
|
||||
availableForReserves--;
|
||||
if (view.rules().enabled(featureExtendedHookState) &&
|
||||
availableForReserves < hookStateScale)
|
||||
return INTERNAL_ERROR;
|
||||
|
||||
availableForReserves -= hookStateScale;
|
||||
stateMap.modified_entry_count++;
|
||||
}
|
||||
|
||||
@@ -1569,11 +1586,17 @@ set_state_cache(
|
||||
auto& stateMapNs = stateMapAcc[ns];
|
||||
if (stateMapNs.find(key) == stateMapNs.end())
|
||||
{
|
||||
// new State Key
|
||||
if (modified)
|
||||
{
|
||||
if (!canReserveNew)
|
||||
return RESERVE_INSUFFICIENT;
|
||||
availableForReserves--;
|
||||
|
||||
if (view.rules().enabled(featureExtendedHookState) &&
|
||||
availableForReserves < hookStateScale)
|
||||
return INTERNAL_ERROR;
|
||||
|
||||
availableForReserves -= hookStateScale;
|
||||
stateMap.modified_entry_count++;
|
||||
}
|
||||
|
||||
@@ -1582,6 +1605,7 @@ set_state_cache(
|
||||
return 1;
|
||||
}
|
||||
|
||||
// existing State Key
|
||||
if (modified)
|
||||
{
|
||||
if (!stateMapNs[key].first)
|
||||
@@ -1670,7 +1694,15 @@ DEFINE_HOOK_FUNCTION(
|
||||
(aread_len && NOT_IN_BOUNDS(aread_ptr, aread_len, memory_length)))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
uint32_t maxSize = hook::maxHookStateDataSize();
|
||||
auto const sleAccount = view.peek(hookCtx.result.accountKeylet);
|
||||
if (!sleAccount && view.rules().enabled(featureExtendedHookState))
|
||||
return tefINTERNAL;
|
||||
|
||||
uint16_t const hookStateScale = sleAccount->isFieldPresent(sfHookStateScale)
|
||||
? sleAccount->getFieldU16(sfHookStateScale)
|
||||
: 1;
|
||||
|
||||
uint32_t maxSize = hook::maxHookStateDataSize(hookStateScale);
|
||||
if (read_len > maxSize)
|
||||
return TOO_BIG;
|
||||
|
||||
@@ -1814,7 +1846,7 @@ hook::finalizeHookState(
|
||||
for (const auto& accEntry : stateMap)
|
||||
{
|
||||
const auto& acc = accEntry.first;
|
||||
for (const auto& nsEntry : std::get<2>(accEntry.second))
|
||||
for (const auto& nsEntry : std::get<3>(accEntry.second))
|
||||
{
|
||||
const auto& ns = nsEntry.first;
|
||||
for (const auto& cacheEntry : nsEntry.second)
|
||||
|
||||
@@ -184,6 +184,19 @@ SetAccount::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// HookStateScale
|
||||
if (tx.isFieldPresent(sfHookStateScale))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureExtendedHookState))
|
||||
return temMALFORMED;
|
||||
|
||||
uint16_t scale = tx.getFieldU16(sfHookStateScale);
|
||||
if (scale == 0 ||
|
||||
scale > hook::maxHookStateScale()) // Min: 1, Max: 16 (256
|
||||
// * 16 = 4096 bytes)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
@@ -249,6 +262,20 @@ SetAccount::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// HookStateScale
|
||||
if (ctx.tx.isFieldPresent(sfHookStateScale))
|
||||
{
|
||||
uint16_t const newScale = ctx.tx.getFieldU16(sfHookStateScale);
|
||||
uint16_t const currentScale = sle->getFieldU16(sfHookStateScale);
|
||||
uint32_t const stateCount = sle->getFieldU32(sfHookStateCount);
|
||||
if (stateCount > 0 && newScale < currentScale)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Cannot decrease HookStateScale if state count is not zero.";
|
||||
return tecHAS_HOOK_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -629,6 +656,50 @@ SetAccount::doApply()
|
||||
if (uFlagsIn != uFlagsOut)
|
||||
sle->setFieldU32(sfFlags, uFlagsOut);
|
||||
|
||||
// HookStateScale
|
||||
if (tx.isFieldPresent(sfHookStateScale))
|
||||
{
|
||||
uint16_t const newScale = tx.getFieldU16(sfHookStateScale);
|
||||
uint16_t const oldScale = sle->isFieldPresent(sfHookStateScale)
|
||||
? sle->getFieldU16(sfHookStateScale)
|
||||
: 1;
|
||||
if (newScale == oldScale)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
else if (newScale == 1)
|
||||
{
|
||||
sle->makeFieldAbsent(sfHookStateScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
// increase OwnerCount
|
||||
uint32_t const stateCount = sle->getFieldU32(sfHookStateCount);
|
||||
uint32_t const oldOwnerCount = sle->getFieldU32(sfOwnerCount);
|
||||
|
||||
uint32_t const newOwnerCount = oldOwnerCount -
|
||||
(oldScale * stateCount) + (newScale * stateCount);
|
||||
|
||||
// sanity check
|
||||
if (newOwnerCount < oldOwnerCount)
|
||||
return tecINTERNAL;
|
||||
|
||||
if (newOwnerCount != oldOwnerCount)
|
||||
{
|
||||
STAmount const balance = STAmount((*sle)[sfBalance]).xrp();
|
||||
XRPAmount const reserve =
|
||||
view().fees().accountReserve(newOwnerCount);
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
adjustOwnerCount(
|
||||
view(), sle, newOwnerCount - oldOwnerCount, j_);
|
||||
}
|
||||
|
||||
sle->setFieldU16(sfHookStateScale, newScale);
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -858,6 +858,9 @@ SetHook::destroyNamespace(
|
||||
bool const fixEnabled = ctx.rules.enabled(fixNSDelete);
|
||||
bool partialDelete = false;
|
||||
uint32_t oldStateCount = sleAccount->getFieldU32(sfHookStateCount);
|
||||
uint16_t scale = sleAccount->isFieldPresent(sfHookStateScale)
|
||||
? sleAccount->getFieldU16(sfHookStateScale)
|
||||
: 1;
|
||||
|
||||
std::vector<uint256> toDelete;
|
||||
toDelete.reserve(sleDir->getFieldV256(sfIndexes).size());
|
||||
@@ -929,6 +932,15 @@ SetHook::destroyNamespace(
|
||||
view.erase(sleItem);
|
||||
}
|
||||
|
||||
if (view.rules().enabled(featureExtendedHookState) &&
|
||||
oldStateCount < toDelete.size())
|
||||
{
|
||||
JLOG(ctx.j.fatal()) << "HookSet(" << hook::log::NSDELETE_COUNT << ")["
|
||||
<< HS_ACC() << "]: DeleteState "
|
||||
<< "stateCount less than zero (overflow)";
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
|
||||
uint32_t stateCount = oldStateCount - toDelete.size();
|
||||
if (stateCount > oldStateCount)
|
||||
{
|
||||
@@ -945,7 +957,18 @@ SetHook::destroyNamespace(
|
||||
sleAccount->setFieldU32(sfHookStateCount, stateCount);
|
||||
|
||||
if (ctx.rules.enabled(fixNSDelete))
|
||||
adjustOwnerCount(view, sleAccount, -toDelete.size(), ctx.j);
|
||||
{
|
||||
auto const ownerCount = sleAccount->getFieldU32(sfOwnerCount);
|
||||
if (view.rules().enabled(featureExtendedHookState) &&
|
||||
ownerCount < toDelete.size() * scale)
|
||||
{
|
||||
JLOG(ctx.j.fatal()) << "HookSet(" << hook::log::NSDELETE_COUNT
|
||||
<< ")[" << HS_ACC() << "]: DeleteState "
|
||||
<< "OwnerCount less than zero (overflow)";
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
adjustOwnerCount(view, sleAccount, -toDelete.size() * scale, ctx.j);
|
||||
}
|
||||
|
||||
if (!partialDelete && sleAccount->isFieldPresent(sfHookNamespaces))
|
||||
hook::removeHookNamespaceEntry(*sleAccount, ns);
|
||||
|
||||
@@ -74,7 +74,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 = 87;
|
||||
static constexpr std::size_t numFeatures = 88;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -375,6 +375,7 @@ extern uint256 const featureDeepFreeze;
|
||||
extern uint256 const featureIOUIssuerWeakTSH;
|
||||
extern uint256 const featureCron;
|
||||
extern uint256 const fixInvalidTxFlags;
|
||||
extern uint256 const featureExtendedHookState;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -354,6 +354,7 @@ extern SF_UINT16 const sfHookStateChangeCount;
|
||||
extern SF_UINT16 const sfHookEmitCount;
|
||||
extern SF_UINT16 const sfHookExecutionIndex;
|
||||
extern SF_UINT16 const sfHookApiVersion;
|
||||
extern SF_UINT16 const sfHookStateScale;
|
||||
|
||||
// 32-bit integers (common)
|
||||
extern SF_UINT32 const sfNetworkID;
|
||||
|
||||
@@ -343,6 +343,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecINSUF_RESERVE_SELLER = 187,
|
||||
tecIMMUTABLE = 188,
|
||||
tecTOO_MANY_REMARKS = 189,
|
||||
tecHAS_HOOK_STATE = 190,
|
||||
tecLAST_POSSIBLE_ENTRY = 255,
|
||||
};
|
||||
|
||||
|
||||
@@ -481,6 +481,7 @@ REGISTER_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::De
|
||||
REGISTER_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FIX (fixInvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo);
|
||||
|
||||
// The following amendments are obsolete, but must remain supported
|
||||
// because they could potentially get enabled.
|
||||
|
||||
@@ -68,6 +68,7 @@ LedgerFormats::LedgerFormats()
|
||||
{sfGovernanceMarks, soeOPTIONAL},
|
||||
{sfAccountIndex, soeOPTIONAL},
|
||||
{sfTouchCount, soeOPTIONAL},
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfCron, soeOPTIONAL},
|
||||
},
|
||||
commonFields);
|
||||
|
||||
@@ -102,6 +102,7 @@ CONSTRUCT_TYPED_SFIELD(sfHookStateChangeCount, "HookStateChangeCount", UINT16,
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookEmitCount, "HookEmitCount", UINT16, 18);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookExecutionIndex, "HookExecutionIndex", UINT16, 19);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookApiVersion, "HookApiVersion", UINT16, 20);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookStateScale, "HookStateScale", UINT16, 21);
|
||||
|
||||
// 32-bit integers (common)
|
||||
CONSTRUCT_TYPED_SFIELD(sfNetworkID, "NetworkID", UINT32, 1);
|
||||
|
||||
@@ -94,6 +94,8 @@ transResults()
|
||||
MAKE_ERROR(tecINSUF_RESERVE_SELLER, "The seller of an object has insufficient reserves, and thus cannot complete the sale."),
|
||||
MAKE_ERROR(tecIMMUTABLE, "The remark is marked immutable on the object, and therefore cannot be updated."),
|
||||
MAKE_ERROR(tecTOO_MANY_REMARKS, "The number of remarks on the object would exceed the limit of 32."),
|
||||
MAKE_ERROR(tecHAS_HOOK_STATE, "Delete all hook state before reducing scale"),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
|
||||
|
||||
@@ -60,6 +60,7 @@ TxFormats::TxFormats()
|
||||
{sfTickSize, soeOPTIONAL},
|
||||
{sfTicketSequence, soeOPTIONAL},
|
||||
{sfNFTokenMinter, soeOPTIONAL},
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
},
|
||||
commonFields);
|
||||
|
||||
|
||||
@@ -879,6 +879,9 @@ public:
|
||||
auto const bob = Account{"bob"};
|
||||
env.fund(XRP(10000), bob);
|
||||
|
||||
auto const carol = Account{"carol"};
|
||||
env.fund(XRP(10000), carol);
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::TransactionType] = jss::SetHook;
|
||||
@@ -962,6 +965,7 @@ public:
|
||||
data[3] == 'u' && data[4] == 'e' && data[5] == '\0');
|
||||
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2);
|
||||
BEAST_EXPECT((*env.le(alice))[sfHookStateCount] == 1);
|
||||
}
|
||||
|
||||
// delete the namespace
|
||||
@@ -990,7 +994,113 @@ public:
|
||||
|
||||
// ensure the state object is gone
|
||||
BEAST_EXPECT(!env.le(stateKeylet));
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == fixNS ? 1 : 2);
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == (fixNS ? 1 : 2));
|
||||
BEAST_EXPECT(!(env.le("alice")->isFieldPresent(sfHookStateCount)));
|
||||
}
|
||||
|
||||
if (env.current()->rules().enabled(featureExtendedHookState))
|
||||
{
|
||||
// Test hook with scaled state data
|
||||
TestHook scaled_state_wasm = wasm[
|
||||
R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t
|
||||
read_len, int64_t error_code); extern int64_t rollback
|
||||
(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t state_set (uint32_t read_ptr, uint32_t
|
||||
read_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_set(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t hook_account(uint32_t, uint32_t);
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
|
||||
#define SBUF(x) x, sizeof(x)
|
||||
#define TOO_BIG -3
|
||||
#define DOESNT_EXIST -5
|
||||
#define KEYLET_ACCOUNT 3
|
||||
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x,sizeof(#x),__LINE__)
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
uint8_t hook_acc[20];
|
||||
ASSERT(hook_account(hook_acc, 20) == 20);
|
||||
uint8_t account_keylet[34];
|
||||
ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT,
|
||||
hook_acc, 20, 0,0,0,0) == 34);
|
||||
|
||||
ASSERT(slot_set(account_keylet, 34, 1) == 1);
|
||||
slot_subfield(1, sfHookStateScale, 2);
|
||||
int64_t scale = slot(0,0,2);
|
||||
|
||||
if (scale == 5) {
|
||||
ASSERT(state_set(0, 256, SBUF("test1")) == 256);
|
||||
ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2);
|
||||
ASSERT(state_set(0, 256*3, SBUF("test3")) == 256*3);
|
||||
ASSERT(state_set(0, 256*4, SBUF("test4")) == 256*4);
|
||||
ASSERT(state_set(0, 256*5, SBUF("test5")) == 256*5);
|
||||
ASSERT(state_set(0, 256*5+1, SBUF("test")) == TOO_BIG);
|
||||
accept(0,0,scale);
|
||||
}
|
||||
rollback(0,0,scale);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
HASH_WASM(scaled_state);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
// Install hook on carol
|
||||
Json::Value jv =
|
||||
ripple::test::jtx::hook(carol, {{hso(scaled_state_wasm)}}, 0);
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str;
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookOn] =
|
||||
to_string(UINT256_BIT[ttACCOUNT_SET]);
|
||||
env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
{
|
||||
// HookStateScale => 5
|
||||
Json::Value jv = noop(carol);
|
||||
jv[sfHookStateScale.fieldName] = 5;
|
||||
env(jv, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
Json::Value invoke = invoke::invoke(carol);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 26);
|
||||
BEAST_EXPECT((*env.le(carol))[sfHookStateCount] == 5);
|
||||
}
|
||||
|
||||
// Delete namespace to clean up state
|
||||
Json::Value iv;
|
||||
iv[jss::Flags] = hsfNSDELETE;
|
||||
iv[jss::HookNamespace] = ns_str;
|
||||
jv[jss::Hooks][0U][jss::Hook] = iv;
|
||||
env(jv, M("Delete namespace"), HSFEE);
|
||||
env.close();
|
||||
|
||||
// Verify state cleanup
|
||||
BEAST_EXPECT(
|
||||
(*env.le(carol))[sfOwnerCount] == features[fixNSDelete] ? 1
|
||||
: 26);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9028,12 +9138,16 @@ public:
|
||||
auto const david = Account{"david"};
|
||||
auto const eve = Account{"eve"}; // small balance
|
||||
auto const frank = Account{"frank"}; // big balance
|
||||
auto const gary = Account{"gary"};
|
||||
auto const hank = Account{"hank"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.fund(XRP(10000), bob);
|
||||
env.fund(XRP(10000), cho);
|
||||
env.fund(XRP(1000000), david);
|
||||
env.fund(XRP(2600), eve);
|
||||
env.fund(XRP(1000000000), frank);
|
||||
env.fund(XRP(10000), gary);
|
||||
env.fund(XRP(10000), hank);
|
||||
|
||||
// install a rollback hook on cho
|
||||
env(ripple::test::jtx::hook(
|
||||
@@ -9058,6 +9172,101 @@ public:
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||
}
|
||||
|
||||
// bounds and buffer size checks
|
||||
{
|
||||
TestHook hook = wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t state_set (
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len
|
||||
);
|
||||
extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x, sizeof(#x), __LINE__);
|
||||
#define TOO_SMALL (-4)
|
||||
#define TOO_BIG (-3)
|
||||
#define OUT_OF_BOUNDS (-1)
|
||||
|
||||
int64_t hook(uint32_t reserved)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
// bounds and buffer size checks
|
||||
{
|
||||
// RH NOTE: readptr/len 0/0 = delete entry
|
||||
|
||||
ASSERT(state_set(0,0,0,0) == TOO_SMALL);
|
||||
ASSERT(state_set(0,0,0,33) == TOO_BIG);
|
||||
ASSERT(state_set(0,0,0,1000000) == TOO_BIG);
|
||||
ASSERT(state_set(0,0,1000000,1) == OUT_OF_BOUNDS);
|
||||
|
||||
ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS);
|
||||
ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS);
|
||||
|
||||
uint16_t size;
|
||||
ASSERT(otxn_param(&size, 2, "SIZE", 4) > 0);
|
||||
ASSERT(state_set(0, size, 0, 32) == TOO_BIG);
|
||||
}
|
||||
accept(0,0,0);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
// install the hook on alice
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0),
|
||||
M("set state_set 1"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
|
||||
// invoke the hook with cho (rollback after alice's hooks have
|
||||
// executed)
|
||||
Json::Value payJv1 = pay(alice, cho, XRP(1));
|
||||
{
|
||||
Json::Value params{Json::arrayValue};
|
||||
params[0U][jss::HookParameter][jss::HookParameterName] =
|
||||
strHex(std::string("SIZE"));
|
||||
params[0U][jss::HookParameter][jss::HookParameterValue] =
|
||||
features[featureExtendedHookState] ? "0108" /* 2049 */
|
||||
: "0101" /* 257 */;
|
||||
payJv1[jss::HookParameters] = params;
|
||||
}
|
||||
env(payJv1,
|
||||
M("test state_set 1 rollback"),
|
||||
fee(XRP(1)),
|
||||
ter(tecHOOK_REJECTED));
|
||||
|
||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||
|
||||
auto const nsdir = env.le(nsdirkl);
|
||||
BEAST_EXPECT(!nsdir);
|
||||
|
||||
auto const state1 = env.le(
|
||||
ripple::keylet::hookState(aliceid, beast::zero, beast::zero));
|
||||
BEAST_EXPECT(!state1);
|
||||
|
||||
// invoke the hook from bob to alice, this will work
|
||||
Json::Value payJv2 = pay(bob, alice, XRP(1));
|
||||
{
|
||||
Json::Value params{Json::arrayValue};
|
||||
params[0U][jss::HookParameter][jss::HookParameterName] =
|
||||
strHex(std::string("SIZE"));
|
||||
params[0U][jss::HookParameter][jss::HookParameterValue] =
|
||||
features[featureExtendedHookState] ? "0108" /* 2049 */
|
||||
: "0101" /* 257 */;
|
||||
payJv2[jss::HookParameters] = params;
|
||||
}
|
||||
env(payJv2, M("test state_set 1"), fee(XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// first hook will set two state objects with different keys and data on
|
||||
// alice
|
||||
{
|
||||
@@ -9094,32 +9303,12 @@ public:
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x, sizeof(#x), __LINE__);
|
||||
#define TOO_SMALL (-4)
|
||||
#define TOO_BIG (-3)
|
||||
#define OUT_OF_BOUNDS (-1)
|
||||
|
||||
#define SBUF(x) (uint32_t)(x), sizeof(x)
|
||||
int64_t hook(uint32_t reserved)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
|
||||
// bounds and buffer size checks
|
||||
{
|
||||
// RH NOTE: readptr/len 0/0 = delete entry
|
||||
|
||||
ASSERT(state_set(0,0,0,0) == TOO_SMALL);
|
||||
ASSERT(state_set(0,0,0,33) == TOO_BIG);
|
||||
ASSERT(state_set(0,0,0,1000000) == TOO_BIG);
|
||||
ASSERT(state_set(0,0,1000000,1) == OUT_OF_BOUNDS);
|
||||
|
||||
ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS);
|
||||
ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS);
|
||||
|
||||
ASSERT(state_set(0, 257, 0, 32) == TOO_BIG);
|
||||
}
|
||||
|
||||
|
||||
// create state 1
|
||||
{
|
||||
uint8_t key[32] =
|
||||
@@ -9135,7 +9324,6 @@ public:
|
||||
0xCAU,0xFEU,0xBAU,0xBEU
|
||||
};
|
||||
|
||||
|
||||
ASSERT(state_set(SBUF(data), SBUF(key)) == sizeof(data));
|
||||
}
|
||||
|
||||
@@ -9146,11 +9334,9 @@ public:
|
||||
1,2,3
|
||||
};
|
||||
|
||||
|
||||
ASSERT(state_set(SBUF(data2), SBUF(key)) == sizeof(data2));
|
||||
}
|
||||
|
||||
|
||||
accept(0,0,0);
|
||||
|
||||
}
|
||||
@@ -9719,6 +9905,210 @@ public:
|
||||
BEAST_EXPECT((*env.le("frank"))[sfOwnerCount] == 260);
|
||||
}
|
||||
|
||||
if (env.current()->rules().enabled(featureExtendedHookState))
|
||||
{
|
||||
// Test hook with scaled state data
|
||||
TestHook scaled_state_wasm = wasm[
|
||||
R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t
|
||||
read_len, int64_t error_code); extern int64_t rollback
|
||||
(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t state_set (uint32_t read_ptr, uint32_t
|
||||
read_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_set(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t hook_account(uint32_t, uint32_t);
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
|
||||
#define SBUF(x) x, sizeof(x)
|
||||
#define TOO_BIG -3
|
||||
#define DOESNT_EXIST -5
|
||||
#define KEYLET_ACCOUNT 3
|
||||
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x,sizeof(#x),__LINE__)
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
uint8_t hook_acc[20];
|
||||
ASSERT(hook_account(hook_acc, 20) == 20);
|
||||
uint8_t account_keylet[34];
|
||||
ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT,
|
||||
hook_acc, 20, 0,0,0,0) == 34);
|
||||
|
||||
ASSERT(slot_set(account_keylet, 34, 1) == 1);
|
||||
slot_subfield(1, sfHookStateScale, 2);
|
||||
int64_t scale = slot(0,0,2);
|
||||
|
||||
if (scale == DOESNT_EXIST) {
|
||||
ASSERT(state_set(0, 256, SBUF("test0")) == 256);
|
||||
ASSERT(state_set(0, 257, SBUF("test")) == TOO_BIG);
|
||||
accept(0,0,scale);
|
||||
}
|
||||
if (scale == 2) {
|
||||
ASSERT(state_set(0, 256, SBUF("test1")) == 256);
|
||||
ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2);
|
||||
ASSERT(state_set(0, 256*2+1, SBUF("test")) ==
|
||||
TOO_BIG); accept(0,0,scale);
|
||||
}
|
||||
if (scale == 5) {
|
||||
ASSERT(state_set(0, 256, SBUF("test3")) == 256);
|
||||
ASSERT(state_set(0, 256*5, SBUF("test4")) == 256*5);
|
||||
ASSERT(state_set(0, 256*5+1, SBUF("test")) ==
|
||||
TOO_BIG); accept(0,0,scale);
|
||||
}
|
||||
rollback(0,0,scale);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
HASH_WASM(scaled_state);
|
||||
BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
// Install hook on carol
|
||||
Json::Value jv =
|
||||
ripple::test::jtx::hook(gary, {{hso(scaled_state_wasm)}}, 0);
|
||||
// jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str;
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookOn] =
|
||||
to_string(UINT256_BIT[ttACCOUNT_SET]);
|
||||
env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 1);
|
||||
BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
{
|
||||
// no HookStateScale
|
||||
Json::Value invoke = invoke::invoke(gary);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 2);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1);
|
||||
}
|
||||
|
||||
{
|
||||
// HookStateScale => 2
|
||||
Json::Value jv = noop(gary);
|
||||
jv[sfHookStateScale.fieldName] = 2;
|
||||
env(jv, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 3);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1);
|
||||
|
||||
Json::Value invoke = invoke::invoke(gary);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 7);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3);
|
||||
}
|
||||
{
|
||||
// HookStateScale => 5
|
||||
Json::Value jv = noop(gary);
|
||||
jv[sfHookStateScale.fieldName] = 5;
|
||||
env(jv, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 16);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3);
|
||||
|
||||
Json::Value invoke = invoke::invoke(gary);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 26);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 5);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bool extHookStateEnabled = features[featureExtendedHookState];
|
||||
// tests for set_state_cache
|
||||
if (extHookStateEnabled)
|
||||
{
|
||||
TestHook extended_state_reserve_hook = wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t state_set (
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len
|
||||
);
|
||||
extern int64_t state_foreign_set (
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t nread_ptr,
|
||||
uint32_t nread_len,
|
||||
uint32_t aread_ptr,
|
||||
uint32_t aread_len
|
||||
);
|
||||
extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
#define RESERVE_INSUFFICIENT -38
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x, sizeof(#x), __LINE__);
|
||||
#define ASSERT_EQUAL(x, y)\
|
||||
if (!(x == y))\
|
||||
rollback((uint32_t)#x, sizeof(#x), x);
|
||||
int64_t hook(uint32_t reserved)
|
||||
{
|
||||
_g(1,1);
|
||||
{
|
||||
// 1. first account for StateMap
|
||||
ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT);
|
||||
// 2. first namespace for StateMap
|
||||
ASSERT_EQUAL(state_foreign_set(0, 1, "1", 1, "1", 32, 0, 0), RESERVE_INSUFFICIENT);
|
||||
// 3. first statekey for StateMap
|
||||
ASSERT_EQUAL(state_set(0, 1, "2", 1), RESERVE_INSUFFICIENT);
|
||||
// 4. existing statedata
|
||||
ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT);
|
||||
}
|
||||
accept(0,0,0);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
// install the hook on gary
|
||||
Json::Value jv = hso(extended_state_reserve_hook, overrideFlag);
|
||||
jv[jss::HookOn] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // only invoke high
|
||||
env(ripple::test::jtx::hook(hank, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
|
||||
Json::Value jv1 = noop(hank);
|
||||
jv1[sfHookStateScale.fieldName] = 8;
|
||||
env(jv1, HSFEE);
|
||||
env.close();
|
||||
|
||||
auto const caller = Account{"caller"};
|
||||
env.fund(XRP(10000), caller);
|
||||
env.close();
|
||||
auto const payAmount = env.balance(hank) -
|
||||
(env.current()->fees().accountReserve(1 + 8)) -
|
||||
drops(1); // 8 + Hook
|
||||
// reduce hank's balance
|
||||
env(pay(hank, Account{"master"}, payAmount), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// invoke the hook from alice
|
||||
Json::Value invokeJv5 = invoke::invoke(caller, hank, "");
|
||||
env(invokeJv5, M("test state_set 15"), fee(XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
// RH TODO:
|
||||
// check state can be set on emit callback
|
||||
// check namespacing provides for non-collision of same key
|
||||
@@ -12884,14 +13274,17 @@ public:
|
||||
using namespace test::jtx;
|
||||
static FeatureBitset const all{supported_amendments()};
|
||||
|
||||
static std::array<FeatureBitset, 6> const feats{
|
||||
static std::array<FeatureBitset, 7> const feats{
|
||||
all,
|
||||
all - fixXahauV2,
|
||||
all - fixXahauV1 - fixXahauV2,
|
||||
all - fixXahauV1 - fixXahauV2 - fixNSDelete,
|
||||
all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap,
|
||||
all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap -
|
||||
featureHookCanEmit};
|
||||
featureHookCanEmit,
|
||||
all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap -
|
||||
featureExtendedHookState,
|
||||
};
|
||||
|
||||
if (BEAST_EXPECT(instance < feats.size()))
|
||||
{
|
||||
@@ -13058,7 +13451,8 @@ SETHOOK_TEST(1, false)
|
||||
SETHOOK_TEST(2, false)
|
||||
SETHOOK_TEST(3, false)
|
||||
SETHOOK_TEST(4, false)
|
||||
SETHOOK_TEST(5, true)
|
||||
SETHOOK_TEST(5, false)
|
||||
SETHOOK_TEST(6, true)
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2);
|
||||
@@ -13066,6 +13460,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2);
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
#undef M
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -544,6 +544,8 @@ public:
|
||||
void
|
||||
testTicket()
|
||||
{
|
||||
testcase("Ticket");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env(*this);
|
||||
Account const alice("alice");
|
||||
@@ -574,6 +576,104 @@ public:
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testHookStateScale()
|
||||
{
|
||||
testcase("HookStateScale");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env(*this, supported_amendments() - featureExtendedHookState);
|
||||
Account const alice("alice");
|
||||
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
// disabled
|
||||
auto jt = noop(alice);
|
||||
jt[sfHookStateScale.fieldName] = 1;
|
||||
env(jt, ter(temMALFORMED));
|
||||
env.close();
|
||||
|
||||
env.enableFeature(featureExtendedHookState);
|
||||
env.close();
|
||||
|
||||
// set invalid HookStateScale (0 or > 16)
|
||||
for (uint16_t scale : {0, 17})
|
||||
{
|
||||
jt[sfHookStateScale.fieldName] = scale;
|
||||
env(jt, ter(temMALFORMED));
|
||||
}
|
||||
|
||||
// set HookStateScale to 1
|
||||
jt[sfHookStateScale.fieldName] = 1;
|
||||
env(jt);
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfHookStateScale));
|
||||
|
||||
// increase HookStateScale to 16
|
||||
jt[sfHookStateScale.fieldName] = 16;
|
||||
env(jt);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(alice)->getFieldU16(sfHookStateScale) == 16);
|
||||
|
||||
// decrease HookStateScale to 8
|
||||
jt[sfHookStateScale.fieldName] = 8;
|
||||
env(jt);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(alice)->getFieldU16(sfHookStateScale) == 8);
|
||||
|
||||
// reset HookStateScale
|
||||
jt[sfHookStateScale.fieldName] = 1;
|
||||
env(jt);
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfHookStateScale));
|
||||
|
||||
// test OwnerCount
|
||||
// This prevents an exception for sfMintedNFTokens when the AccountRoot
|
||||
// template is applied.
|
||||
{
|
||||
uint256 const nftId0{token::getNextID(env, alice, 0u)};
|
||||
env(token::mint(alice, 0u));
|
||||
env(token::burn(alice, nftId0));
|
||||
env.close();
|
||||
}
|
||||
auto applyCount = [&](uint16_t scale,
|
||||
uint32_t stateCount,
|
||||
uint32_t ownerCount) {
|
||||
return env.app().openLedger().modify(
|
||||
[&](OpenView& view, beast::Journal j) -> bool {
|
||||
auto const sle = view.read(keylet::account(alice.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
auto replacement = std::make_shared<SLE>(*sle, sle->key());
|
||||
(*replacement)[sfHookStateScale] = scale;
|
||||
(*replacement)[sfHookStateCount] = stateCount;
|
||||
(*replacement)[sfOwnerCount] = ownerCount;
|
||||
view.rawReplace(replacement);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
applyCount(5, 10, 100);
|
||||
|
||||
// remove, but HookStateCount exists
|
||||
jt[sfHookStateScale.fieldName] = 1;
|
||||
env(jt, ter(tecHAS_HOOK_STATE));
|
||||
// decrease, but HookStateCount exists
|
||||
jt[sfHookStateScale.fieldName] = 4;
|
||||
env(jt, ter(tecHAS_HOOK_STATE));
|
||||
// increase
|
||||
jt[sfHookStateScale.fieldName] = 6;
|
||||
env(jt, ter(tesSUCCESS));
|
||||
BEAST_EXPECT(env.le(alice)->getFieldU16(sfHookStateScale) == 6);
|
||||
BEAST_EXPECT(env.le(alice)->getFieldU32(sfHookStateCount) == 10);
|
||||
BEAST_EXPECT(env.le(alice)->getFieldU32(sfOwnerCount) == 110);
|
||||
|
||||
// check InsufficientReserve
|
||||
applyCount(1, 100, 200);
|
||||
jt[sfHookStateScale.fieldName] = 16;
|
||||
env(jt, ter(tecINSUFFICIENT_RESERVE));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -590,6 +690,7 @@ public:
|
||||
testRequireAuthWithDir();
|
||||
testTransferRate();
|
||||
testTicket();
|
||||
testHookStateScale();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user