diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs index caf79778b86..57de57dbcd1 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs @@ -2073,8 +2073,16 @@ pub(super) fn apply_can_be_deleted_by_moderators( pub(super) fn parse_can_be_deleted_by_moderators_for_keyword( schema: &Value, ) -> Result, ProtocolError> { - schema - .get_optional_integer::(CAN_BE_DELETED_BY_MODERATORS_FOR) + // A schema that is not an object carries no keyword. Like every other + // doctype-level keyword read before the core parser, this one must not be + // the first to fail on such a schema: the core parser refuses it as an + // invalid contract structure, and a raw value error here would replace + // that refusal. + let Ok(schema_map) = schema.to_map() else { + return Ok(None); + }; + + Value::inner_optional_integer_value::(schema_map, CAN_BE_DELETED_BY_MODERATORS_FOR) .map_err(consensus_or_protocol_value_error) } diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/moderators_delete_tests.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/moderators_delete_tests.rs index 36f8ab58762..e019653d73d 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/moderators_delete_tests.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/moderators_delete_tests.rs @@ -320,3 +320,33 @@ fn should_refuse_a_window_that_is_not_a_positive_number_of_seconds_on_the_stored } } } + +#[test] +fn should_refuse_a_schema_that_is_not_an_object_as_an_invalid_contract_structure() { + // The window is read off the raw schema before the core parser checks that + // the schema is an object. It must not be the reader that fails first: a + // schema that is no object carries no keyword, and the core parser is the + // one that names the real problem. + let platform_version = PlatformVersion::latest(); + for full_validation in [true, false] { + for schema in [ + platform_value!(null), + platform_value!("post"), + platform_value!([]), + ] { + let error = parse_with_config( + schema.clone(), + &moderated_config(platform_version), + platform_version.protocol_version, + full_validation, + ) + .expect_err("a schema that is not an object must be refused"); + let message = format!("{error:?}"); + assert!( + message.contains("InvalidContractStructure"), + "schema {schema:?} must be refused as an invalid contract structure (full \ + validation: {full_validation}), got {message}" + ); + } + } +}