Files
rippled/src/tests/libxrpl/tx/wasm/host_functions/SetDataNestedObjectField.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

95 lines
3.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_object_field and its reader — one level of nesting inside an account's
// data object.
//
// The two string arguments are the outer key then the inner one, in that order on the wire.
// A contract that swaps them writes somewhere else entirely, which is why the order is
// asserted rather than assumed.
struct SetDataNestedObjectFieldImpl : ContractHostFixture
{
Account const alice = fund("alice");
Account const contract = fund("contract");
ContractHost
host()
{
return makeContractHost({.contractAccount = contract.id(), .caller = alice.id()});
}
};
TEST_F(SetDataNestedObjectFieldImpl, WhatIsStoredIsWhatIsReadBack)
{
auto const contractHost = host();
expectValue(contractHost->setDataNestedObjectField(alice.id(), "stats", "score", u32(9999)), 0);
expectValue(
contractHost->getDataNestedObjectField(alice.id(), "stats", "score"),
serialization(u32(9999)));
}
// The outer key comes first. Read under the swapped pair and there is nothing there, which
// is what a contract that got the order wrong would see.
TEST_F(SetDataNestedObjectFieldImpl, TheOuterKeyIsTheFirstOne)
{
auto const contractHost = host();
ASSERT_TRUE(contractHost->setDataNestedObjectField(alice.id(), "stats", "score", u32(1)));
expectError(
contractHost->getDataNestedObjectField(alice.id(), "score", "stats"),
HostFunctionError::InvalidField);
}
TEST_F(SetDataNestedObjectFieldImpl, OneObjectHoldsSeveralKeys)
{
auto const contractHost = host();
ASSERT_TRUE(contractHost->setDataNestedObjectField(alice.id(), "stats", "score", u32(1)));
ASSERT_TRUE(contractHost->setDataNestedObjectField(alice.id(), "stats", "level", u32(2)));
expectValue(
contractHost->getDataNestedObjectField(alice.id(), "stats", "score"),
serialization(u32(1)));
expectValue(
contractHost->getDataNestedObjectField(alice.id(), "stats", "level"),
serialization(u32(2)));
}
TEST_F(SetDataNestedObjectFieldImpl, WhatIsFinalizedIsVisibleToTheNextCall)
{
{
auto const contractHost = host();
ASSERT_TRUE(
contractHost->setDataNestedObjectField(alice.id(), "stats", "score", u32(9999)));
ASSERT_EQ(contractHost.finalize(), tesSUCCESS);
}
expectValue(
host()->getDataNestedObjectField(alice.id(), "stats", "score"), serialization(u32(9999)));
}
TEST_F(SetDataNestedObjectFieldImpl, AnArrayIsNotAnObject)
{
auto const contractHost = host();
ASSERT_TRUE(contractHost->setDataArrayElementField(alice.id(), 0, "first", u8(1)));
expectError(
contractHost->setDataNestedObjectField(alice.id(), "stats", "score", u32(1)),
HostFunctionError::InvalidState);
}
TEST_F(SetDataNestedObjectFieldImpl, AnAccountThatDoesNotExistCannotBeWrittenTo)
{
expectError(
host()->setDataNestedObjectField(Account{"ghost"}.id(), "stats", "score", u32(1)),
HostFunctionError::InvalidAccount);
}
} // namespace xrpl::test