commit b16fd1880772aae5b8b6834c52fdaae60d82cac5
parent eb709b2020a7929b72ec90a5724da511bdf8eb07
Author: triesap <tyson@radroots.org>
Date: Sat, 21 Feb 2026 23:30:44 +0000
tests: extend structured encode and decode edge coverage
- add required-field error assertions across document, farm, coop, plot, resource area, and resource cap encoders
- add list set encode and decode edge cases for duplicate d tags and invalid entry payloads
- add message file, follow, job feedback, and resource area list set helper coverage for optional and invalid paths
- verify with cargo check, cargo test, and sdk coverage progress reporting for `radroots-events-codec`
Diffstat:
6 files changed, 554 insertions(+), 0 deletions(-)
diff --git a/crates/events-codec/src/resource_area/list_sets.rs b/crates/events-codec/src/resource_area/list_sets.rs
@@ -157,3 +157,50 @@ where
image: None,
})
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn resource_list_set_id_validates_suffix() {
+ let err = resource_list_set_id("AAAAAAAAAAAAAAAAAAAAAw", " ")
+ .expect_err("expected empty suffix error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("list_set_suffix")
+ ));
+ }
+
+ #[test]
+ fn list_entries_rejects_empty_values() {
+ let err = list_entries("p", [" "]).expect_err("expected empty entry error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("entry.values")
+ ));
+ }
+
+ #[test]
+ fn farm_and_plot_address_helpers_reject_empty_d_tags() {
+ let err = farm_address(&RadrootsFarmRef {
+ pubkey: "farm_pubkey".to_string(),
+ d_tag: " ".to_string(),
+ })
+ .expect_err("expected farm d_tag error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("farm.d_tag")
+ ));
+
+ let err = plot_address(&RadrootsPlotRef {
+ pubkey: "plot_pubkey".to_string(),
+ d_tag: " ".to_string(),
+ })
+ .expect_err("expected plot d_tag error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("plot.d_tag")
+ ));
+ }
+}
diff --git a/crates/events-codec/tests/follow.rs b/crates/events-codec/tests/follow.rs
@@ -343,3 +343,66 @@ fn follow_to_wire_parts_with_kind_and_after_mutation_work() {
assert_eq!(toggled.kind, KIND_FOLLOW);
assert_eq!(toggled.tags.len(), 2);
}
+
+#[test]
+fn follow_apply_normalizes_optional_fields_and_deduplicates_existing_list() {
+ let follow = RadrootsFollow {
+ list: vec![
+ RadrootsFollowProfile {
+ published_at: 1,
+ public_key: " pubkey-a ".to_string(),
+ relay_url: Some(" ".to_string()),
+ contact_name: Some(" ".to_string()),
+ },
+ RadrootsFollowProfile {
+ published_at: 2,
+ public_key: "pubkey-a".to_string(),
+ relay_url: Some("wss://duplicate.example.com".to_string()),
+ contact_name: Some("duplicate".to_string()),
+ },
+ ],
+ };
+
+ let updated = follow_apply(
+ &follow,
+ FollowMutation::Follow {
+ public_key: "pubkey-a".to_string(),
+ relay_url: Some(" ".to_string()),
+ contact_name: Some(" ".to_string()),
+ },
+ )
+ .unwrap();
+
+ assert_eq!(updated.list.len(), 1);
+ assert_eq!(updated.list[0].public_key, "pubkey-a");
+ assert!(updated.list[0].relay_url.is_none());
+ assert!(updated.list[0].contact_name.is_none());
+}
+
+#[test]
+fn follow_apply_follow_with_none_preserves_existing_values() {
+ let follow = RadrootsFollow {
+ list: vec![RadrootsFollowProfile {
+ published_at: 1,
+ public_key: "pubkey-a".to_string(),
+ relay_url: Some("wss://relay.example.com".to_string()),
+ contact_name: Some("alice".to_string()),
+ }],
+ };
+
+ let updated = follow_apply(
+ &follow,
+ FollowMutation::Follow {
+ public_key: "pubkey-a".to_string(),
+ relay_url: None,
+ contact_name: None,
+ },
+ )
+ .unwrap();
+ assert_eq!(updated.list.len(), 1);
+ assert_eq!(
+ updated.list[0].relay_url.as_deref(),
+ Some("wss://relay.example.com")
+ );
+ assert_eq!(updated.list[0].contact_name.as_deref(), Some("alice"));
+}
diff --git a/crates/events-codec/tests/job_feedback.rs b/crates/events-codec/tests/job_feedback.rs
@@ -80,3 +80,47 @@ fn job_feedback_metadata_rejects_wrong_kind() {
JobParseError::InvalidTag("kind (expected 7000)")
));
}
+
+#[test]
+fn job_feedback_build_tags_cover_optional_paths() {
+ let mut fb = sample_feedback();
+ fb.extra_info = None;
+ fb.payment = None;
+ fb.request_event.relays = None;
+ fb.customer_pubkey = None;
+ fb.encrypted = true;
+ let parts = to_wire_parts(&fb, "payload").unwrap();
+
+ let status = parts
+ .tags
+ .iter()
+ .find(|tag| tag.first().map(|v| v.as_str()) == Some("status"))
+ .expect("status tag");
+ assert_eq!(status.len(), 2);
+
+ let request = parts
+ .tags
+ .iter()
+ .find(|tag| tag.first().map(|v| v.as_str()) == Some("e"))
+ .expect("request tag");
+ assert_eq!(request.len(), 2);
+
+ assert!(
+ !parts
+ .tags
+ .iter()
+ .any(|tag| tag.first().map(|v| v.as_str()) == Some("amount"))
+ );
+ assert!(
+ !parts
+ .tags
+ .iter()
+ .any(|tag| tag.first().map(|v| v.as_str()) == Some("p"))
+ );
+ assert!(
+ parts
+ .tags
+ .iter()
+ .any(|tag| tag.first().map(|v| v.as_str()) == Some("encrypted"))
+ );
+}
diff --git a/crates/events-codec/tests/list_set.rs b/crates/events-codec/tests/list_set.rs
@@ -81,6 +81,30 @@ fn list_set_encode_and_decode_reject_invalid_inputs() {
got: KIND_POST
}
));
+
+ let mut invalid_entry_tag = sample_list_set();
+ invalid_entry_tag.entries[0].tag = " ".to_string();
+ let err = list_set_build_tags(&invalid_entry_tag).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("entry.tag")
+ ));
+
+ let mut invalid_entry_values = sample_list_set();
+ invalid_entry_values.entries[0].values.clear();
+ let err = list_set_build_tags(&invalid_entry_values).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("entry.values")
+ ));
+
+ let mut invalid_entry_first = sample_list_set();
+ invalid_entry_first.entries[0].values = vec![" ".to_string()];
+ let err = list_set_build_tags(&invalid_entry_first).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("entry.values")
+ ));
}
#[test]
@@ -103,6 +127,17 @@ fn list_set_decode_rejects_invalid_tag_shapes() {
)
.unwrap_err();
assert!(matches!(err, EventParseError::InvalidTag("tag")));
+
+ let err = list_set_from_tags(
+ KIND_LIST_SET_FOLLOW,
+ "".to_string(),
+ &[
+ vec!["d".to_string(), "members.owners".to_string()],
+ vec!["p".to_string(), " ".to_string()],
+ ],
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("tag")));
}
#[test]
@@ -157,3 +192,14 @@ fn list_set_decode_keeps_first_optional_display_tags() {
assert_eq!(decoded.description.as_deref(), Some("team"));
assert_eq!(decoded.image.as_deref(), Some("https://example.com/a.png"));
}
+
+#[test]
+fn list_set_decode_keeps_first_d_tag() {
+ let tags = vec![
+ vec!["d".to_string(), "members.owners".to_string()],
+ vec!["d".to_string(), "members.ignore".to_string()],
+ vec!["p".to_string(), "owner".to_string()],
+ ];
+ let decoded = list_set_from_tags(KIND_LIST_SET_FOLLOW, "private".to_string(), &tags).unwrap();
+ assert_eq!(decoded.d_tag, "members.owners");
+}
diff --git a/crates/events-codec/tests/message_file.rs b/crates/events-codec/tests/message_file.rs
@@ -207,6 +207,85 @@ fn message_file_from_tags_rejects_invalid_optional_tags() {
)
.unwrap_err();
assert!(matches!(err, EventParseError::InvalidTag("fallback")));
+
+ let err = message_file_from_tags(
+ KIND_MESSAGE_FILE,
+ &[
+ vec!["p".to_string(), "pub1".to_string()],
+ vec!["file-type".to_string(), " ".to_string()],
+ vec!["encryption-algorithm".to_string(), "aes-gcm".to_string()],
+ vec!["decryption-key".to_string(), "key".to_string()],
+ vec!["decryption-nonce".to_string(), "nonce".to_string()],
+ vec!["x".to_string(), "hash".to_string()],
+ ],
+ "https://files.example/encrypted.bin",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("file-type")));
+
+ let err = message_file_from_tags(
+ KIND_MESSAGE_FILE,
+ &[
+ vec!["p".to_string(), "pub1".to_string()],
+ vec!["file-type".to_string(), "image/jpeg".to_string()],
+ vec!["encryption-algorithm".to_string(), "aes-gcm".to_string()],
+ vec!["decryption-key".to_string(), "key".to_string()],
+ vec!["decryption-nonce".to_string(), "nonce".to_string()],
+ vec!["x".to_string(), "hash".to_string()],
+ vec!["size".to_string(), " ".to_string()],
+ ],
+ "https://files.example/encrypted.bin",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("size")));
+
+ let err = message_file_from_tags(
+ KIND_MESSAGE_FILE,
+ &[
+ vec!["p".to_string(), "pub1".to_string()],
+ vec!["file-type".to_string(), "image/jpeg".to_string()],
+ vec!["encryption-algorithm".to_string(), "aes-gcm".to_string()],
+ vec!["decryption-key".to_string(), "key".to_string()],
+ vec!["decryption-nonce".to_string(), "nonce".to_string()],
+ vec!["x".to_string(), "hash".to_string()],
+ vec!["dim".to_string(), " ".to_string()],
+ ],
+ "https://files.example/encrypted.bin",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("dim")));
+
+ let err = message_file_from_tags(
+ KIND_MESSAGE_FILE,
+ &[
+ vec!["p".to_string(), "pub1".to_string()],
+ vec!["file-type".to_string(), "image/jpeg".to_string()],
+ vec!["encryption-algorithm".to_string(), "aes-gcm".to_string()],
+ vec!["decryption-key".to_string(), "key".to_string()],
+ vec!["decryption-nonce".to_string(), "nonce".to_string()],
+ vec!["x".to_string(), "hash".to_string()],
+ vec!["thumb".to_string(), " ".to_string()],
+ ],
+ "https://files.example/encrypted.bin",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("thumb")));
+
+ let err = message_file_from_tags(
+ KIND_MESSAGE_FILE,
+ &[
+ vec!["p".to_string(), "pub1".to_string()],
+ vec!["file-type".to_string(), "image/jpeg".to_string()],
+ vec!["encryption-algorithm".to_string(), "aes-gcm".to_string()],
+ vec!["decryption-key".to_string(), "key".to_string()],
+ vec!["decryption-nonce".to_string(), "nonce".to_string()],
+ vec!["x".to_string(), "hash".to_string()],
+ vec!["fallback".to_string(), " ".to_string()],
+ ],
+ "https://files.example/encrypted.bin",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("fallback")));
}
#[test]
@@ -243,3 +322,21 @@ fn message_file_metadata_and_index_from_event_roundtrip() {
assert_eq!(index.event.sig, "sig");
assert_eq!(index.metadata.message_file.file_type, "image/jpeg");
}
+
+#[test]
+fn message_file_from_tags_rejects_empty_content() {
+ let err = message_file_from_tags(
+ KIND_MESSAGE_FILE,
+ &[
+ vec!["p".to_string(), "pub1".to_string()],
+ vec!["file-type".to_string(), "image/jpeg".to_string()],
+ vec!["encryption-algorithm".to_string(), "aes-gcm".to_string()],
+ vec!["decryption-key".to_string(), "key".to_string()],
+ vec!["decryption-nonce".to_string(), "nonce".to_string()],
+ vec!["x".to_string(), "hash".to_string()],
+ ],
+ " ",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("content")));
+}
diff --git a/crates/events-codec/tests/structured_encode_default.rs b/crates/events-codec/tests/structured_encode_default.rs
@@ -332,6 +332,263 @@ fn structured_build_tags_cover_optional_and_error_paths() {
}
#[test]
+fn structured_build_tags_cover_required_field_errors() {
+ let document = RadrootsDocument {
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(),
+ doc_type: "charter".to_string(),
+ title: "Charter".to_string(),
+ version: "1.0.0".to_string(),
+ summary: None,
+ effective_at: None,
+ body_markdown: None,
+ subject: RadrootsDocumentSubject {
+ pubkey: TEST_PUBKEY_HEX.to_string(),
+ address: Some(
+ "30340:58e318557257f2ab58a415d21bb57082b4824cf667a1d64e72bcbc5acc018c62:AAAAAAAAAAAAAAAAAAAAAA"
+ .to_string(),
+ ),
+ },
+ tags: None,
+ };
+ let document_tags = document_build_tags(&document).unwrap();
+ assert!(document_tags.iter().any(|tag| tag[0] == "a"));
+
+ let mut invalid_document = document.clone();
+ invalid_document.d_tag = " ".to_string();
+ let err = document_build_tags(&invalid_document).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
+ invalid_document = document.clone();
+ invalid_document.doc_type = " ".to_string();
+ let err = document_build_tags(&invalid_document).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("doc_type")));
+ invalid_document = document.clone();
+ invalid_document.title = " ".to_string();
+ let err = document_build_tags(&invalid_document).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("title")));
+ invalid_document = document.clone();
+ invalid_document.version = " ".to_string();
+ let err = document_build_tags(&invalid_document).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("version")));
+ invalid_document = document.clone();
+ invalid_document.subject.pubkey = " ".to_string();
+ let err = document_build_tags(&invalid_document).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("subject.pubkey")
+ ));
+
+ let farm = RadrootsFarm {
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
+ name: "Farm".to_string(),
+ about: None,
+ website: None,
+ picture: None,
+ banner: None,
+ location: None,
+ tags: None,
+ };
+ let mut invalid_farm = farm.clone();
+ invalid_farm.d_tag = " ".to_string();
+ let err = farm_build_tags(&invalid_farm).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
+ invalid_farm = farm.clone();
+ invalid_farm.name = " ".to_string();
+ let err = farm_build_tags(&invalid_farm).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
+ let err = farm_ref_tags(&RadrootsFarmRef {
+ pubkey: " ".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
+ })
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("farm.pubkey")
+ ));
+ let err = farm_ref_tags(&RadrootsFarmRef {
+ pubkey: TEST_PUBKEY_HEX.to_string(),
+ d_tag: " ".to_string(),
+ })
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("farm.d_tag")
+ ));
+
+ let coop = RadrootsCoop {
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
+ name: "Coop".to_string(),
+ about: None,
+ website: None,
+ picture: None,
+ banner: None,
+ location: None,
+ tags: None,
+ };
+ let mut invalid_coop = coop.clone();
+ invalid_coop.d_tag = " ".to_string();
+ let err = coop_build_tags(&invalid_coop).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
+ invalid_coop = coop.clone();
+ invalid_coop.name = " ".to_string();
+ let err = coop_build_tags(&invalid_coop).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
+ invalid_coop = coop.clone();
+ invalid_coop.location = Some(RadrootsCoopLocation {
+ primary: None,
+ city: None,
+ region: None,
+ country: None,
+ gcs: RadrootsGcsLocation {
+ geohash: " ".to_string(),
+ ..sample_gcs()
+ },
+ });
+ let err = coop_build_tags(&invalid_coop).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("location.gcs.geohash")
+ ));
+ let err = coop_ref_tags(&RadrootsCoopRef {
+ pubkey: " ".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
+ })
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("coop.pubkey")
+ ));
+ let err = coop_ref_tags(&RadrootsCoopRef {
+ pubkey: TEST_PUBKEY_HEX.to_string(),
+ d_tag: " ".to_string(),
+ })
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("coop.d_tag")
+ ));
+
+ let plot = RadrootsPlot {
+ d_tag: "AAAAAAAAAAAAAAAAAAAABQ".to_string(),
+ farm: RadrootsFarmRef {
+ pubkey: TEST_PUBKEY_HEX.to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
+ },
+ name: "Plot".to_string(),
+ about: None,
+ location: None,
+ tags: None,
+ };
+ let mut invalid_plot = plot.clone();
+ invalid_plot.d_tag = " ".to_string();
+ let err = plot_build_tags(&invalid_plot).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
+ invalid_plot = plot.clone();
+ invalid_plot.name = " ".to_string();
+ let err = plot_build_tags(&invalid_plot).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
+ invalid_plot = plot.clone();
+ invalid_plot.farm.pubkey = " ".to_string();
+ let err = plot_build_tags(&invalid_plot).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("farm.pubkey")
+ ));
+ invalid_plot = plot.clone();
+ invalid_plot.farm.d_tag = " ".to_string();
+ let err = plot_build_tags(&invalid_plot).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("farm.d_tag")
+ ));
+ let err = plot_address(TEST_PUBKEY_HEX, " ").unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("plot.d_tag")));
+
+ let area = RadrootsResourceArea {
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(),
+ name: "Area".to_string(),
+ about: None,
+ location: RadrootsResourceAreaLocation {
+ primary: None,
+ city: None,
+ region: None,
+ country: None,
+ gcs: sample_gcs(),
+ },
+ tags: None,
+ };
+ let mut invalid_area = area.clone();
+ invalid_area.d_tag = " ".to_string();
+ let err = resource_area_build_tags(&invalid_area).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
+ invalid_area = area.clone();
+ invalid_area.name = " ".to_string();
+ let err = resource_area_build_tags(&invalid_area).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
+ let err = resource_area_ref_tags(&RadrootsResourceAreaRef {
+ pubkey: " ".to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(),
+ })
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("resource_area.pubkey")
+ ));
+ let err = resource_area_ref_tags(&RadrootsResourceAreaRef {
+ pubkey: TEST_PUBKEY_HEX.to_string(),
+ d_tag: " ".to_string(),
+ })
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("resource_area.d_tag")
+ ));
+
+ let cap = RadrootsResourceHarvestCap {
+ d_tag: "AAAAAAAAAAAAAAAAAAAABA".to_string(),
+ resource_area: RadrootsResourceAreaRef {
+ pubkey: TEST_PUBKEY_HEX.to_string(),
+ d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(),
+ },
+ product: RadrootsResourceHarvestProduct {
+ key: "nutmeg".to_string(),
+ category: Some("spice".to_string()),
+ },
+ start: 1,
+ end: 2,
+ cap_quantity: RadrootsCoreQuantity::new(
+ RadrootsCoreDecimal::from(1000u32),
+ RadrootsCoreUnit::MassG,
+ ),
+ display_amount: None,
+ display_unit: None,
+ display_label: None,
+ tags: None,
+ };
+ let mut invalid_cap = cap.clone();
+ invalid_cap.d_tag = " ".to_string();
+ let err = resource_harvest_cap_build_tags(&invalid_cap).unwrap_err();
+ assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
+ invalid_cap = cap.clone();
+ invalid_cap.resource_area.pubkey = " ".to_string();
+ let err = resource_harvest_cap_build_tags(&invalid_cap).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("resource_area.pubkey")
+ ));
+ invalid_cap = cap.clone();
+ invalid_cap.resource_area.d_tag = " ".to_string();
+ let err = resource_harvest_cap_build_tags(&invalid_cap).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("resource_area.d_tag")
+ ));
+ let mut no_category = cap.clone();
+ no_category.product.category = Some(" ".to_string());
+ let tags = resource_harvest_cap_build_tags(&no_category).unwrap();
+ assert!(!tags.iter().any(|tag| tag[0] == "category"));
+}
+
+#[test]
fn structured_list_sets_cover_success_and_error_paths() {
let farm_id = "AAAAAAAAAAAAAAAAAAAAAA";
let members = farm_members_list_set(farm_id, [TEST_PUBKEY_HEX]).unwrap();