Add Vector256 support to the locator (#6131)

* add Vector256 nesting/length support

* [WIP] add tests

* fix tests

* simplify with helper function

* oops typo

* remove static variable

* respond to comments

* STBaseOrUInt256->FieldValue

* oops

* add more tests for coverage

* respond to comments
This commit is contained in:
Mayukha Vadari
2026-01-15 20:14:42 -05:00
committed by GitHub
parent 209a1a6ffa
commit 9a9a7aab01
2 changed files with 145 additions and 47 deletions

View File

@@ -29,6 +29,12 @@ toBytes(std::uint32_t value)
return Bytes{b, e};
}
static Bytes
toBytes(uint256 const& value)
{
return Bytes{value.begin(), value.end()};
}
static Bytes
toBytes(Asset const& asset)
{
@@ -303,6 +309,11 @@ struct HostFuncImpl_test : public beast::unit_test::suite
testcase("getTxField");
using namespace test::jtx;
std::string const credIdHex =
"0011223344556677889900112233445566778899001122334455667788990011";
uint256 credId;
BEAST_EXPECT(credId.parseHex(credIdHex));
Env env{*this};
OpenView ov{*env.current()};
STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) {
@@ -310,6 +321,9 @@ struct HostFuncImpl_test : public beast::unit_test::suite
obj.setAccountID(sfOwner, env.master.id());
obj.setFieldU32(sfOfferSequence, env.seq(env.master));
obj.setFieldArray(sfMemos, STArray{});
STVector256 credIds;
credIds.push_back(credId);
obj.setFieldV256(sfCredentialIDs, credIds);
});
ApplyContext ac = createApplyContext(env, ov, stx);
auto const dummyEscrow =
@@ -340,6 +354,12 @@ struct HostFuncImpl_test : public beast::unit_test::suite
BEAST_EXPECT(
memos.error() == HostFunctionError::NOT_LEAF_FIELD);
auto const credentialIds = hfs.getTxField(sfCredentialIDs);
if (BEAST_EXPECT(!credentialIds.has_value()))
BEAST_EXPECTS(
credentialIds.error() == HostFunctionError::NOT_LEAF_FIELD,
std::to_string(HfErrorToInt(credentialIds.error())));
auto const nonField = hfs.getTxField(sfInvalid);
if (BEAST_EXPECT(!nonField.has_value()))
BEAST_EXPECT(
@@ -450,6 +470,15 @@ struct HostFuncImpl_test : public beast::unit_test::suite
BEAST_EXPECT(*amountField == toBytes(XRP(100)));
}
// Should return the PreviousTxnID field from the escrow ledger object
auto const previousTxnId =
hfs.getCurrentLedgerObjField(sfPreviousTxnID);
if (BEAST_EXPECT(previousTxnId.has_value()))
{
BEAST_EXPECT(
*previousTxnId == toBytes(env.tx()->getTransactionID()));
}
// Should return nullopt for a field not present
auto const notPresent = hfs.getCurrentLedgerObjField(sfOwner);
BEAST_EXPECT(
@@ -541,6 +570,11 @@ struct HostFuncImpl_test : public beast::unit_test::suite
Env env{*this};
OpenView ov{*env.current()};
std::string const credIdHex =
"0011223344556677889900112233445566778899001122334455667788990011";
uint256 credId;
BEAST_EXPECT(credId.parseHex(credIdHex));
// Create a transaction with a nested array field
STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) {
obj.setAccountID(sfAccount, env.master.id());
@@ -549,6 +583,9 @@ struct HostFuncImpl_test : public beast::unit_test::suite
memoObj.setFieldVL(sfMemoData, Slice("hello", 5));
memos.push_back(memoObj);
obj.setFieldArray(sfMemos, memos);
STVector256 credIds;
credIds.push_back(credId);
obj.setFieldV256(sfCredentialIDs, credIds);
});
ApplyContext ac = createApplyContext(env, ov, stx);
@@ -578,6 +615,24 @@ struct HostFuncImpl_test : public beast::unit_test::suite
}
}
{
// Locator for sfCredentialIDs[0]
std::vector<int32_t> locatorVec = {sfCredentialIDs.fieldCode, 0};
Slice locator(
reinterpret_cast<uint8_t const*>(locatorVec.data()),
locatorVec.size() * sizeof(int32_t));
auto const result = hfs.getTxNestedField(locator);
if (BEAST_EXPECTS(
result.has_value(),
std::to_string(static_cast<int>(result.error()))))
{
std::string credIdResult(
result.value().begin(), result.value().end());
BEAST_EXPECT(strHex(credIdResult) == credIdHex);
}
}
{
// can use the nested locator for base fields too
std::vector<int32_t> locatorVec = {sfAccount.fieldCode};
@@ -637,6 +692,11 @@ struct HostFuncImpl_test : public beast::unit_test::suite
sfMemoData.fieldCode},
HostFunctionError::INDEX_OUT_OF_BOUNDS);
// Locator for non-existent index
expectError(
{sfCredentialIDs.fieldCode, 1}, // index 1 does not exist
HostFunctionError::INDEX_OUT_OF_BOUNDS);
// Locator for non-existent nested field
expectError(
{sfMemos.fieldCode,
@@ -661,6 +721,10 @@ struct HostFuncImpl_test : public beast::unit_test::suite
// Locator for STArray
expectError({sfMemos.fieldCode}, HostFunctionError::NOT_LEAF_FIELD);
// Locator for STVector256
expectError(
{sfCredentialIDs.fieldCode}, HostFunctionError::NOT_LEAF_FIELD);
// Locator for nesting into non-array/object field
expectError(
{sfAccount.fieldCode, // sfAccount is not an array or object
@@ -956,6 +1020,11 @@ struct HostFuncImpl_test : public beast::unit_test::suite
testcase("getTxArrayLen");
using namespace test::jtx;
std::string const credIdHex =
"0011223344556677889900112233445566778899001122334455667788990011";
uint256 credId;
BEAST_EXPECT(credId.parseHex(credIdHex));
Env env{*this};
OpenView ov{*env.current()};
@@ -974,6 +1043,9 @@ struct HostFuncImpl_test : public beast::unit_test::suite
memos.push_back(memoObj);
}
obj.setFieldArray(sfMemos, memos);
STVector256 credIds;
credIds.push_back(credId);
obj.setFieldV256(sfCredentialIDs, credIds);
});
ApplyContext ac = createApplyContext(env, ov, stx);
@@ -996,6 +1068,11 @@ struct HostFuncImpl_test : public beast::unit_test::suite
if (BEAST_EXPECT(!missingArray.has_value()))
BEAST_EXPECT(
missingArray.error() == HostFunctionError::FIELD_NOT_FOUND);
// Should return 1 for sfCredentialIDs
auto const credIdsLen = hfs.getTxArrayLen(sfCredentialIDs);
if (BEAST_EXPECT(credIdsLen.has_value()))
BEAST_EXPECT(credIdsLen.value() == 1);
}
void
@@ -2195,9 +2272,9 @@ struct HostFuncImpl_test : public beast::unit_test::suite
// clang-format on
void
testFloatTrace()
testTraceFloat()
{
testcase("FloatTrace");
testcase("traceFloat");
using namespace test::jtx;
{
@@ -2251,7 +2328,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
void
testFloatFromInt()
{
testcase("FloatFromInt");
testcase("floatFromInt");
using namespace test::jtx;
Env env{*this};
@@ -2298,7 +2375,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
void
testFloatFromUint()
{
testcase("FloatFromUint");
testcase("floatFromUint");
using namespace test::jtx;
Env env{*this};
@@ -2339,7 +2416,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
void
testFloatSet()
{
testcase("FloatSet");
testcase("floatSet");
using namespace test::jtx;
Env env{*this};
@@ -2426,7 +2503,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
void
testFloatCompare()
{
testcase("FloatCompare");
testcase("floatCompare");
using namespace test::jtx;
Env env{*this};
@@ -3071,7 +3148,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
void
testFloats()
{
testFloatTrace();
testTraceFloat();
testFloatFromInt();
testFloatFromUint();
testFloatSet();

View File

@@ -5,12 +5,13 @@
namespace xrpl {
typedef std::variant<STBase const*, uint256 const*> FieldValue;
namespace detail {
static Expected<Bytes, HostFunctionError>
getAnyFieldData(STBase const* obj)
{
// auto const& fname = obj.getFName();
if (!obj)
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
@@ -25,6 +26,7 @@ getAnyFieldData(STBase const* obj)
// LCOV_EXCL_STOP
case STI_OBJECT:
case STI_ARRAY:
case STI_VECTOR256:
return Unexpected(HostFunctionError::NOT_LEAF_FIELD);
break;
case STI_ACCOUNT: {
@@ -62,6 +64,7 @@ getAnyFieldData(STBase const* obj)
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
return Bytes{b, e};
}
break;
case STI_UINT32: {
auto const* num(static_cast<STInteger<std::uint32_t> const*>(obj));
std::uint32_t const data = num->value();
@@ -70,6 +73,12 @@ getAnyFieldData(STBase const* obj)
return Bytes{b, e};
}
break;
case STI_UINT256: {
auto const* uint256Obj(static_cast<STUInt256 const*>(obj));
auto const& data = uint256Obj->value();
return Bytes{data.begin(), data.end()};
}
break;
default:
break; // default to serializer
}
@@ -81,6 +90,21 @@ getAnyFieldData(STBase const* obj)
return data;
}
static Expected<Bytes, HostFunctionError>
getAnyFieldData(FieldValue const& variantObj)
{
if (STBase const* const* obj = std::get_if<STBase const*>(&variantObj))
{
return getAnyFieldData(*obj);
}
else if (uint256 const* const* u = std::get_if<uint256 const*>(&variantObj))
{
return Bytes((*u)->begin(), (*u)->end());
}
return Unexpected(HostFunctionError::INTERNAL); // LCOV_EXCL_LINE
}
static inline bool
noField(STBase const* field)
{
@@ -88,7 +112,7 @@ noField(STBase const* field)
(STI_UNKNOWN == field->getSType());
}
static Expected<STBase const*, HostFunctionError>
static Expected<FieldValue, HostFunctionError>
locateField(STObject const& obj, Slice const& locator)
{
if (locator.empty() || (locator.size() & 3)) // must be multiple of 4
@@ -141,6 +165,13 @@ locateField(STObject const& obj, Slice const& locator)
auto const& fname(*it->second);
field = o->peekAtPField(fname);
}
else if (STI_VECTOR256 == field->getSType())
{
auto const* v = static_cast<STVector256 const*>(field);
if (sfieldCode >= v->size())
return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS);
return FieldValue(&(v->operator[](sfieldCode)));
}
else // simple field must be the last one
{
return Unexpected(HostFunctionError::LOCATOR_MALFORMED);
@@ -150,7 +181,22 @@ locateField(STObject const& obj, Slice const& locator)
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
}
return field;
return FieldValue(field);
}
static inline Expected<int32_t, HostFunctionError>
getArrayLen(FieldValue const& variantField)
{
if (STBase const* const* field = std::get_if<STBase const*>(&variantField))
{
if ((*field)->getSType() == STI_VECTOR256)
return static_cast<STVector256 const*>(*field)->size();
if ((*field)->getSType() == STI_ARRAY)
return static_cast<STArray const*>(*field)->size();
}
// uint256 is not an array so that variant should still return NO_ARRAY
return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE
}
} // namespace detail
@@ -256,24 +302,20 @@ WasmHostFunctionsImpl::getLedgerObjNestedField(
Expected<int32_t, HostFunctionError>
WasmHostFunctionsImpl::getTxArrayLen(SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256)
return Unexpected(HostFunctionError::NO_ARRAY);
auto const* field = ctx.tx.peekAtPField(fname);
if (detail::noField(field))
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
if (field->getSType() != STI_ARRAY)
return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
return detail::getArrayLen(field);
}
Expected<int32_t, HostFunctionError>
WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256)
return Unexpected(HostFunctionError::NO_ARRAY);
auto const sle = getCurrentLedgerObj();
@@ -284,11 +326,7 @@ WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname)
if (detail::noField(field))
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
if (field->getSType() != STI_ARRAY)
return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
return detail::getArrayLen(field);
}
Expected<int32_t, HostFunctionError>
@@ -296,7 +334,7 @@ WasmHostFunctionsImpl::getLedgerObjArrayLen(
int32_t cacheIdx,
SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256)
return Unexpected(HostFunctionError::NO_ARRAY);
auto const normalizedIdx = normalizeCacheIndex(cacheIdx);
@@ -307,12 +345,7 @@ WasmHostFunctionsImpl::getLedgerObjArrayLen(
if (detail::noField(field))
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
if (field->getSType() != STI_ARRAY)
return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
return detail::getArrayLen(field);
}
// Subsection: nested array length getters
@@ -324,12 +357,8 @@ WasmHostFunctionsImpl::getTxNestedArrayLen(Slice const& locator)
if (!r)
return Unexpected(r.error());
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return Unexpected(HostFunctionError::NO_ARRAY);
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
auto const& field = r.value();
return detail::getArrayLen(field);
}
Expected<int32_t, HostFunctionError>
@@ -342,12 +371,8 @@ WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(Slice const& locator)
if (!r)
return Unexpected(r.error());
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return Unexpected(HostFunctionError::NO_ARRAY);
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
auto const& field = r.value();
return detail::getArrayLen(field);
}
Expected<int32_t, HostFunctionError>
@@ -363,12 +388,8 @@ WasmHostFunctionsImpl::getLedgerObjNestedArrayLen(
if (!r)
return Unexpected(r.error());
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return Unexpected(HostFunctionError::NO_ARRAY);
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
auto const& field = r.value();
return detail::getArrayLen(field);
}
} // namespace xrpl