#include #include #include #include #include #include #include #include #include namespace xrpl::test { // The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust // side, not here. struct TxNestedFieldCall : HostContextTest { std::vector const steps{5, -12, 130}; Bytes const locatorBytes = bytesOfSteps(steps); }; TEST_F(TxNestedFieldCall, LocatorBytesBecomeFieldLocatorHostIsAskedFor) { Bytes const value{1, 2, 3}; EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(value)); OutRegion out{32}; EXPECT_EQ( hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()), static_cast(value.size())); EXPECT_TRUE(out.holds(bytesOf(value))); } TEST_F(TxNestedFieldCall, HostErrorBecomesContractReturnValue) { EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))) .WillOnce(testing::Return(std::unexpected(HostFunctionError::NotLeafField))); OutRegion out{32}; EXPECT_EQ( hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()), hfErrorToInt(HostFunctionError::NotLeafField)); EXPECT_FALSE(out.wasWritten()); } TEST_F(TxNestedFieldCall, EmptyLocatorIsRefusedWithoutAskingHost) { EXPECT_CALL(host, getTxNestedField).Times(0); OutRegion out{32}; EXPECT_EQ( hostContext.getTxNestedField(bytesOf(Bytes{}), out.slice()), hfErrorToInt(HostFunctionError::LocatorMalformed)); } // Distinct from an empty locator: `invokeWithLocator` checks the two conditions separately. TEST_F(TxNestedFieldCall, MisalignedLocatorLengthIsRefusedWithoutAskingHost) { Bytes const oddLength{1, 2, 3}; EXPECT_CALL(host, getTxNestedField).Times(0); OutRegion out{32}; EXPECT_EQ( hostContext.getTxNestedField(bytesOf(oddLength), out.slice()), hfErrorToInt(HostFunctionError::LocatorMalformed)); } TEST_F(TxNestedFieldCall, HostExceptionBecomesInternalFatalAndIsLogged) { EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))) .WillOnce(testing::Throw(std::runtime_error{"nested field came apart"})); OutRegion out{32}; EXPECT_EQ( hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()), hfErrorToInt(HostFunctionError::InternalFatal)); EXPECT_THAT(logged(), testing::HasSubstr("nested field came apart")); EXPECT_THAT(logged(), testing::HasSubstr("getTxNestedField")); } // The out-region contract: write only if the whole value fits, and return the true length // either way. TEST_F(TxNestedFieldCall, ShortOutRegionWritesNothingAndReturnsTrueLength) { Bytes const value{1, 2, 3}; EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(value)); OutRegion out{value.size() - 1}; EXPECT_EQ( hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()), static_cast(value.size())); EXPECT_FALSE(out.wasWritten()); } TEST_F(TxNestedFieldCall, OutRegionOfExactSizeIsWritten) { Bytes const value{1, 2, 3}; EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(value)); OutRegion out{value.size()}; EXPECT_EQ( hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()), static_cast(value.size())); EXPECT_TRUE(out.holds(bytesOf(value))); } TEST_F(TxNestedFieldCall, EmptyResultAnswersZeroAndWritesNothing) { EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(Bytes{})); OutRegion out{32}; EXPECT_EQ(hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()), 0); EXPECT_FALSE(out.wasWritten()); } } // namespace xrpl::test