Files
rippled/src/tests/libxrpl/tx/wasm/host_functions/SetDataNestedArrayElementField.cpp
Mayukha Vadari 77d8e5bf2e Test the data-shaped and transaction-shaped contract host functions
Completes the `host_functions/` layer: reads, nested objects, arrays,
nested arrays, and the two emit paths, each against a real ledger.

What the shapes turn out to mean, now asserted rather than assumed:

- An account has one data object, and the first write decides whether it
  is a map of keys or a list of elements. Mixing them is refused rather
  than silently replacing one with the other.
- The two string arguments of a nested write are the outer key then the
  inner one. Reading under the swapped pair finds nothing, which is what
  a contract that got the order wrong would see.
- Two arrays under two keys are two arrays.
- A stored integer reads back big-endian, the opposite of the
  little-endian a parameter answers with.

Two more defects are pinned rather than fixed, both the same shape as the
one in the previous commit: reconstructing an `STTx` re-runs its format
check, and the throw lands outside the guard that would have answered
`SubmitTxnFailure`. `emit_txn` cannot emit a transaction that does not
already carry `tfInnerBatchTxn`, because setting the flag means rebuilding
it, and an ordinary Payment's defaulted `sfPaths` is rejected on the way
back in. The contract is told `InternalFatal` and the run stops.

`ContractLedger` now gives each host a transaction of its own; two
identical ones share an id, which a ledger refuses to record twice.
2026-09-15 18:32:08 -04:00

118 lines
4.1 KiB
C++

#include <xrpl/protocol/STJson.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <gtest/gtest.h>
#include <tx/wasm/fixtures/ContractHostFixture.h>
namespace xrpl::test {
// set_data_nested_array_element_field and its reader — an array held under a key of an
// account's data object, rather than the object itself being one.
//
// The arguments are the outer key, the element index, and the key inside that element: the
// index sits between the two strings on the wire, which is the shape nothing else has.
struct SetDataNestedArrayElementFieldImpl : ContractHostFixture
{
Account const alice = fund("alice");
Account const contract = fund("contract");
ContractHost
host()
{
return makeContractHost({.contractAccount = contract.id(), .caller = alice.id()});
}
};
TEST_F(SetDataNestedArrayElementFieldImpl, EachElementKeepsItsOwnValue)
{
auto const contractHost = host();
expectValue(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 0, "id", u32(55)), 0);
expectValue(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 1, "id", u32(77)), 0);
expectValue(
contractHost->getDataNestedArrayElementField(alice.id(), "items", 0, "id"),
serialization(u32(55)));
expectValue(
contractHost->getDataNestedArrayElementField(alice.id(), "items", 1, "id"),
serialization(u32(77)));
}
TEST_F(SetDataNestedArrayElementFieldImpl, OneElementHoldsSeveralKeys)
{
auto const contractHost = host();
ASSERT_TRUE(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 0, "id", u32(55)));
ASSERT_TRUE(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 0, "price", u32(12)));
expectValue(
contractHost->getDataNestedArrayElementField(alice.id(), "items", 0, "price"),
serialization(u32(12)));
}
// Two arrays under two keys are two arrays: an element of one is not an element of the
// other, which is what having the outer key on the wire is for.
TEST_F(SetDataNestedArrayElementFieldImpl, ArraysUnderDifferentKeysAreSeparate)
{
auto const contractHost = host();
ASSERT_TRUE(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 0, "id", u32(55)));
ASSERT_TRUE(
contractHost->setDataNestedArrayElementField(alice.id(), "orders", 0, "id", u32(77)));
expectValue(
contractHost->getDataNestedArrayElementField(alice.id(), "items", 0, "id"),
serialization(u32(55)));
expectValue(
contractHost->getDataNestedArrayElementField(alice.id(), "orders", 0, "id"),
serialization(u32(77)));
}
TEST_F(SetDataNestedArrayElementFieldImpl, WhatIsFinalizedIsVisibleToTheNextCall)
{
{
auto const contractHost = host();
ASSERT_TRUE(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 0, "id", u32(55)));
ASSERT_EQ(contractHost.finalize(), tesSUCCESS);
}
expectValue(
host()->getDataNestedArrayElementField(alice.id(), "items", 0, "id"),
serialization(u32(55)));
}
// The data object holds the array, so the object itself may not be one.
TEST_F(SetDataNestedArrayElementFieldImpl, AnArrayRootHasNoKeyToHoldAnArrayUnder)
{
auto const contractHost = host();
ASSERT_TRUE(contractHost->setDataArrayElementField(alice.id(), 0, "first", u8(1)));
expectError(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 0, "id", u32(55)),
HostFunctionError::InvalidState);
}
TEST_F(SetDataNestedArrayElementFieldImpl, AnElementThatWasNeverWrittenIsNotThere)
{
auto const contractHost = host();
ASSERT_TRUE(
contractHost->setDataNestedArrayElementField(alice.id(), "items", 0, "id", u32(55)));
expectError(
contractHost->getDataNestedArrayElementField(alice.id(), "items", 1, "id"),
HostFunctionError::InvalidField);
}
TEST_F(SetDataNestedArrayElementFieldImpl, AnAccountThatDoesNotExistCannotBeWrittenTo)
{
expectError(
host()->setDataNestedArrayElementField(Account{"ghost"}.id(), "items", 0, "id", u32(1)),
HostFunctionError::InvalidAccount);
}
} // namespace xrpl::test