mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-28 07:48:01 +00:00
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.
95 lines
3.1 KiB
C++
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
|