use super::*;
#[test]
fn namespace_dry_run_and_submitted_formats() {
let base = NamespaceOutcome {
operator: None,
registry: Address::repeat_byte(0x01),
status: NamespaceStatus::DryRun,
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &base, false).unwrap();
let dry = String::from_utf8(buf).unwrap();
assert!(dry.contains("status=dry_run submitted=false"), "{dry}");
assert!(!dry.contains("operator="), "{dry}");
assert!(!dry.contains("namespace_id="), "{dry}");
let done = NamespaceOutcome {
operator: Some(Address::repeat_byte(0xCD)),
status: NamespaceStatus::Created {
tx: B256::repeat_byte(0x55),
id: Some(9),
},
..base
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &done, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("operator=0xcdcd"), "{s}");
assert!(s.contains("namespace_id=9"), "{s}");
assert!(s.contains("status=created tx=0x5555"), "{s}");
}
#[test]
fn namespace_dry_run_carries_no_tx_and_is_not_submitted() {
let dry = NamespaceOutcome {
operator: None,
registry: Address::repeat_byte(0x01),
status: NamespaceStatus::DryRun,
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &dry, false).unwrap();
let text = String::from_utf8(buf).unwrap();
assert!(!text.contains("tx=0x"), "{text}");
assert!(text.contains("status=dry_run submitted=false"), "{text}");
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &dry, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("dry_run"));
assert_eq!(v["submitted"], serde_json::json!(false));
assert!(v["tx"].is_null(), "{v}");
}
#[test]
fn namespace_output_distinguishes_created_unknown_and_failed() {
let o = NamespaceOutcome {
operator: Some(Address::repeat_byte(0xCD)),
registry: Address::repeat_byte(0x01),
status: NamespaceStatus::Created {
tx: B256::repeat_byte(0x55),
id: None,
},
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("status=created tx=0x5555"), "{s}");
assert!(!s.contains("dry_run"), "{s}");
assert!(!s.contains("namespace_id="), "{s}");
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &o, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("created"));
assert_eq!(v["submitted"], serde_json::json!(true));
assert!(v["namespace_id"].is_null(), "{v}");
assert_eq!(
v["tx"],
serde_json::json!(format!("{:#x}", B256::repeat_byte(0x55)))
);
let unknown = NamespaceOutcome {
status: NamespaceStatus::InFlight(B256::repeat_byte(0x55)),
..o
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &unknown, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("unknown"));
assert_eq!(
v["tx"],
serde_json::json!(format!("{:#x}", B256::repeat_byte(0x55)))
);
let failed = NamespaceOutcome {
status: NamespaceStatus::Failed,
..o
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &failed, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("failed"));
assert_eq!(v["submitted"], serde_json::json!(false));
}
#[test]
fn namespace_output_surfaces_reverted_and_maybe_broadcast() {
let base = NamespaceOutcome {
operator: Some(Address::repeat_byte(0xCD)),
registry: Address::repeat_byte(0x01),
status: NamespaceStatus::Reverted(B256::repeat_byte(0x55)),
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &base, false).unwrap();
let text = String::from_utf8(buf).unwrap();
assert!(text.contains("status=reverted tx=0x5555"), "{text}");
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &base, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("reverted"));
assert_eq!(v["submitted"], serde_json::json!(true));
assert_eq!(
v["tx"],
serde_json::json!(format!("{:#x}", B256::repeat_byte(0x55)))
);
let maybe = NamespaceOutcome {
status: NamespaceStatus::MaybeBroadcast,
..base
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &maybe, false).unwrap();
let text = String::from_utf8(buf).unwrap();
assert!(
text.contains("status=maybe_broadcast submitted=unknown"),
"{text}"
);
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &maybe, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("maybe_broadcast"));
assert!(
v["submitted"].is_null(),
"submitted must be null, not false: {v}"
);
assert!(v["tx"].is_null(), "{v}");
}
#[test]
fn namespace_created_with_id_emits_the_id() {
let o = NamespaceOutcome {
operator: Some(Address::repeat_byte(0xCD)),
registry: Address::repeat_byte(0x01),
status: NamespaceStatus::Created {
tx: B256::repeat_byte(0x55),
id: Some(9),
},
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("namespace_id=9"), "{s}");
assert!(s.contains("status=created tx=0x5555"), "{s}");
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &o, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("created"));
assert_eq!(v["namespace_id"], serde_json::json!(9));
}
#[test]
fn namespace_failure_context_only_claims_a_namespace_when_one_exists() {
let in_flight =
namespace_failure_context(NamespaceStatus::InFlight(B256::repeat_byte(0x55))).unwrap();
assert!(in_flight.contains("was broadcast"), "{in_flight}");
assert!(
in_flight.contains("maxNamespacesPerPublisher"),
"{in_flight}"
);
let landed = namespace_failure_context(NamespaceStatus::Created {
tx: B256::repeat_byte(0x55),
id: None,
})
.unwrap();
assert!(landed.contains("exists and is owned"), "{landed}");
assert!(
landed.contains("NamespaceCreated logs for this signer"),
"{landed}"
);
assert!(!landed.contains("ownerOf"), "{landed}");
let maybe = namespace_failure_context(NamespaceStatus::MaybeBroadcast).unwrap();
assert!(maybe.contains("may have reached the node"), "{maybe}");
assert!(maybe.contains("this signer's pending"), "{maybe}");
assert!(maybe.contains("maxNamespacesPerPublisher"), "{maybe}");
assert!(
namespace_failure_context(NamespaceStatus::Failed).is_none(),
"a rejected send must not claim a created namespace"
);
assert!(
namespace_failure_context(NamespaceStatus::Reverted(B256::repeat_byte(0x55))).is_none(),
"a reverted create minted nothing"
);
assert!(
namespace_failure_context(NamespaceStatus::DryRun).is_none(),
"a dry run must not claim a created namespace"
);
}
fn receipt_with_logs(logs: Vec<alloy::rpc::types::Log>) -> alloy::rpc::types::TransactionReceipt {
alloy::rpc::types::TransactionReceipt {
inner: alloy::consensus::ReceiptEnvelope::Eip1559(alloy::consensus::ReceiptWithBloom {
receipt: alloy::consensus::Receipt {
status: alloy::consensus::Eip658Value::Eip658(true),
cumulative_gas_used: 0,
logs,
},
logs_bloom: alloy::primitives::Bloom::ZERO,
}),
transaction_hash: B256::repeat_byte(0x55),
transaction_index: Some(0),
block_hash: None,
block_number: None,
gas_used: 0,
effective_gas_price: 0,
blob_gas_used: None,
blob_gas_price: None,
from: Address::ZERO,
to: None,
contract_address: None,
}
}
fn log_with_topics(topics: Vec<B256>) -> alloy::rpc::types::Log {
alloy::rpc::types::Log {
inner: alloy::primitives::Log {
address: Address::repeat_byte(0x01),
data: alloy::primitives::LogData::new_unchecked(
topics,
alloy::primitives::Bytes::new(),
),
},
block_hash: None,
block_number: None,
block_timestamp: None,
transaction_hash: None,
transaction_index: None,
log_index: None,
removed: false,
}
}
fn namespace_created_log(id: U256, owner: Address) -> alloy::rpc::types::Log {
log_with_topics(vec![
PublisherRegistry::NamespaceCreated::SIGNATURE_HASH,
B256::from(id),
owner.into_word(),
])
}
#[test]
fn decode_created_namespace_reads_the_id_out_of_the_log() {
let receipt = receipt_with_logs(vec![namespace_created_log(
U256::from(9),
Address::repeat_byte(0xCD),
)]);
assert_eq!(decode_created_namespace(&receipt).unwrap(), 9);
}
#[test]
fn decode_created_namespace_reports_a_receipt_with_no_matching_log() {
let receipt = receipt_with_logs(vec![log_with_topics(vec![B256::repeat_byte(0xAB)])]);
let err = format!("{:#}", decode_created_namespace(&receipt).unwrap_err());
assert!(err.contains("carried no NamespaceCreated"), "{err}");
}
#[test]
fn decode_created_namespace_separates_an_undecodable_log_from_a_missing_one() {
let receipt = receipt_with_logs(vec![log_with_topics(vec![
PublisherRegistry::NamespaceCreated::SIGNATURE_HASH,
])]);
let err = format!("{:#}", decode_created_namespace(&receipt).unwrap_err());
assert!(err.contains("failed to decode"), "{err}");
assert!(!err.contains("carried no NamespaceCreated"), "{err}");
}
#[test]
fn decode_created_namespace_rejects_an_id_that_does_not_fit_u64() {
let receipt = receipt_with_logs(vec![namespace_created_log(
U256::from(u64::MAX) + U256::from(1),
Address::repeat_byte(0xCD),
)]);
let err = format!("{:#}", decode_created_namespace(&receipt).unwrap_err());
assert!(err.contains("exceeds u64"), "{err}");
}
#[test]
fn assign_output_lists_every_seat_with_its_tx() {
let a = Address::repeat_byte(0x11);
let b = Address::repeat_byte(0x22);
let o = AssignOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: vec![
(a, SeatOutcome::Seated(B256::repeat_byte(0x55))),
(b, SeatOutcome::Seated(B256::repeat_byte(0x66))),
],
dry_run: false,
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("operators=2"), "{s}");
assert!(s.contains("tx=0x5555"), "{s}");
assert!(s.contains("tx=0x6666"), "{s}");
assert!(s.contains("status=seated seated=2"), "{s}");
}
#[test]
fn assign_output_distinguishes_partial_from_seated() {
let landed = Address::repeat_byte(0x11);
let failed = Address::repeat_byte(0x22);
let untried = Address::repeat_byte(0x33);
let o = AssignOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: vec![
(landed, SeatOutcome::Seated(B256::repeat_byte(0x55))),
(failed, SeatOutcome::Failed),
(untried, SeatOutcome::NotAttempted),
],
dry_run: false,
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains(&format!(" operator={landed:#x} tx=")), "{s}");
assert!(
s.contains(&format!(" operator={failed:#x} state=failed")),
"{s}"
);
assert!(
s.contains(&format!(" operator={untried:#x} state=not_attempted")),
"{s}"
);
assert!(s.contains("status=partial seated=1"), "{s}");
}
#[test]
fn assign_output_reports_failed_when_no_seat_landed() {
let o = AssignOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: vec![
(Address::repeat_byte(0x11), SeatOutcome::Failed),
(Address::repeat_byte(0x22), SeatOutcome::NotAttempted),
],
dry_run: false,
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("status=failed seated=0"), "{s}");
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("failed"));
assert_eq!(v["submitted"], serde_json::json!(false));
}
#[test]
fn assign_output_surfaces_an_in_flight_transaction() {
let landed = Address::repeat_byte(0x11);
let unknown = Address::repeat_byte(0x22);
let o = AssignOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: vec![
(landed, SeatOutcome::Seated(B256::repeat_byte(0x55))),
(unknown, SeatOutcome::InFlight(B256::repeat_byte(0x66))),
],
dry_run: false,
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(
s.contains(&format!(" operator={unknown:#x} tx=0x6666")),
"the in-flight hash must reach the receipt, not just the error chain: {s}"
);
assert!(s.contains("state=in_flight"), "{s}");
assert!(s.contains("status=unknown"), "{s}");
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("unknown"));
assert_eq!(v["submitted"], serde_json::json!(true));
let origins = v["origins"].as_array().unwrap();
assert_eq!(origins[1]["state"], serde_json::json!("in_flight"));
assert_eq!(
origins[1]["tx"],
serde_json::json!(format!("{:#x}", B256::repeat_byte(0x66)))
);
}
#[test]
fn assign_seats_distinguish_reverted_and_maybe_broadcast_from_failed() {
let reverted_only = AssignOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: vec![(
Address::repeat_byte(0x11),
SeatOutcome::Reverted(B256::repeat_byte(0x66)),
)],
dry_run: false,
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &reverted_only, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("failed"));
let origins = v["origins"].as_array().unwrap();
assert_eq!(origins[0]["state"], serde_json::json!("reverted"));
assert_eq!(
origins[0]["tx"],
serde_json::json!(format!("{:#x}", B256::repeat_byte(0x66))),
"a mined revert must keep its hash: {v}"
);
let maybe = AssignOutcome {
seats: vec![(Address::repeat_byte(0x22), SeatOutcome::MaybeBroadcast)],
..reverted_only
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &maybe, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("unknown"));
let origins = v["origins"].as_array().unwrap();
assert_eq!(origins[0]["state"], serde_json::json!("maybe_broadcast"));
assert!(origins[0]["tx"].is_null(), "no hash was captured: {v}");
}
#[test]
fn assign_status_does_not_call_an_empty_run_seated() {
let empty = AssignOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: Vec::new(),
dry_run: false,
};
assert_eq!(empty.status(), "failed");
}
#[test]
fn assign_dry_run_omits_signer_and_txs() {
let o = AssignOutcome {
operator: None,
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: vec![(Address::repeat_byte(0x11), SeatOutcome::NotAttempted)],
dry_run: true,
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("status=dry_run submitted=false"), "{s}");
assert!(!s.lines().any(|l| l.starts_with("operator=")), "{s}");
assert!(!s.contains("tx="), "{s}");
}
#[test]
fn propagate_prefers_the_chain_error_over_a_broken_pipe() {
let broken = || io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe");
let err = propagate(
Some(anyhow::anyhow!("addOrigin reverted")),
Some(broken()),
"w",
"nothing was sent",
)
.unwrap_err();
let rendered = format!("{err:#}");
assert!(rendered.contains("addOrigin reverted"), "{rendered}");
assert!(rendered.contains("could not be written"), "{rendered}");
let err = propagate(
None,
Some(broken()),
"failed to write assign output",
"nothing was sent",
)
.unwrap_err();
assert!(
format!("{err:#}").contains("failed to write assign output"),
"{err:#}"
);
assert!(propagate(None, None, "w", "nothing was sent").is_ok());
}
#[test]
fn propagate_carries_the_receipt_when_stdout_could_not_take_it() {
let broken = || io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe");
let receipt = single_tx_receipt(
Some("namespace_id=9".to_string()),
Some(B256::repeat_byte(0x55)),
);
let err = propagate(None, Some(broken()), "failed to write output", &receipt).unwrap_err();
let rendered = format!("{err:#}");
assert!(rendered.contains("namespace_id=9"), "{rendered}");
assert!(rendered.contains("tx=0x5555"), "{rendered}");
let err = propagate(
Some(anyhow::anyhow!("no NamespaceCreated log")),
Some(broken()),
"failed to write output",
&single_tx_receipt(None, Some(B256::repeat_byte(0x55))),
)
.unwrap_err();
let rendered = format!("{err:#}");
assert!(rendered.contains("no NamespaceCreated log"), "{rendered}");
assert!(rendered.contains("tx=0x5555"), "{rendered}");
}
#[test]
fn single_tx_receipt_names_only_what_is_known() {
let hash = B256::repeat_byte(0x55);
assert_eq!(
single_tx_receipt(Some("namespace_id=9".to_string()), Some(hash)),
format!("namespace_id=9 tx={hash:#x}")
);
assert_eq!(single_tx_receipt(None, Some(hash)), format!("tx={hash:#x}"));
assert_eq!(single_tx_receipt(None, None), "nothing was sent");
}
#[test]
fn revoke_failure_context_only_warns_once_something_was_broadcast() {
let in_flight =
revoke_failure_context(RevokeStatus::InFlight(B256::repeat_byte(0x55))).unwrap();
assert!(in_flight.contains("was broadcast"), "{in_flight}");
assert!(in_flight.contains("NotAuthorizedOrigin"), "{in_flight}");
let maybe = revoke_failure_context(RevokeStatus::MaybeBroadcast).unwrap();
assert!(maybe.contains("may have reached the node"), "{maybe}");
assert!(maybe.contains("NotAuthorizedOrigin"), "{maybe}");
assert!(
revoke_failure_context(RevokeStatus::Failed).is_none(),
"a rejected send unseated nothing and has no hash to cite"
);
assert!(
revoke_failure_context(RevokeStatus::Reverted(B256::repeat_byte(0x55))).is_none(),
"a confirmed revert unseated nothing"
);
assert!(
revoke_failure_context(RevokeStatus::Revoked(B256::repeat_byte(0x55))).is_none(),
"a confirmed removal needs no extra warning"
);
assert!(
revoke_failure_context(RevokeStatus::DryRun).is_none(),
"a dry run sent nothing"
);
}
#[test]
fn revoke_output_names_the_unseated_operator() {
let o = RevokeOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
revoked: Address::repeat_byte(0x11),
status: RevokeStatus::Revoked(B256::repeat_byte(0x55)),
};
let mut buf = Vec::new();
write_revoke_outcome(&mut buf, &o, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("namespace_id=7"), "{s}");
assert!(s.contains("revoked_operator=0x1111"), "{s}");
assert!(s.contains("status=revoked tx=0x5555"), "{s}");
let dry = RevokeOutcome {
operator: None,
status: RevokeStatus::DryRun,
..o
};
let mut buf = Vec::new();
write_revoke_outcome(&mut buf, &dry, false).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("status=dry_run submitted=false"), "{s}");
}
#[test]
fn namespace_json_round_trips() {
let dry = NamespaceOutcome {
operator: None,
registry: Address::repeat_byte(0x01),
status: NamespaceStatus::DryRun,
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &dry, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["submitted"], serde_json::json!(false));
assert_eq!(v["status"], serde_json::json!("dry_run"));
assert!(v["tx"].is_null(), "{v}");
assert!(v["operator"].is_null(), "{v}");
assert!(v["namespace_id"].is_null(), "{v}");
let done = NamespaceOutcome {
operator: Some(Address::repeat_byte(0xCD)),
registry: Address::repeat_byte(0x01),
status: NamespaceStatus::Created {
tx: B256::repeat_byte(0x55),
id: Some(9),
},
};
let mut buf = Vec::new();
write_namespace_outcome(&mut buf, &done, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["submitted"], serde_json::json!(true));
assert_eq!(v["status"], serde_json::json!("created"));
assert_eq!(v["namespace_id"], serde_json::json!(9)); assert_eq!(
v["operator"],
serde_json::json!(format!("{:#x}", done.operator.unwrap()))
);
}
#[test]
fn assign_json_round_trips() {
let landed = Address::repeat_byte(0x11);
let untried = Address::repeat_byte(0x22);
let o = AssignOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
seats: vec![
(landed, SeatOutcome::Seated(B256::repeat_byte(0x55))),
(untried, SeatOutcome::NotAttempted),
],
dry_run: false,
};
let mut buf = Vec::new();
write_assign_outcome(&mut buf, &o, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("partial"));
assert_eq!(v["submitted"], serde_json::json!(true));
assert_eq!(v["operators"].as_array().unwrap().len(), 2);
let origins = v["origins"].as_array().unwrap();
assert_eq!(origins.len(), 2);
assert_eq!(
origins[0]["operator"],
serde_json::json!(format!("{landed:#x}"))
);
assert_eq!(origins[0]["state"], serde_json::json!("seated"));
assert_eq!(
origins[0]["tx"],
serde_json::json!(format!("{:#x}", B256::repeat_byte(0x55)))
);
assert_eq!(origins[1]["state"], serde_json::json!("not_attempted"));
assert!(origins[1]["tx"].is_null(), "{v}");
}
#[test]
fn single_tx_commands_report_an_unreadable_broadcast_as_unknown() {
let revoke = RevokeOutcome {
operator: Some(Address::repeat_byte(0xCD)),
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
revoked: Address::repeat_byte(0x11),
status: RevokeStatus::InFlight(B256::repeat_byte(0x55)),
};
let mut buf = Vec::new();
write_revoke_outcome(&mut buf, &revoke, false).unwrap();
let text = String::from_utf8(buf).unwrap();
assert!(text.contains("status=unknown tx=0x5555"), "{text}");
assert!(!text.contains("status=revoked"), "{text}");
let mut buf = Vec::new();
write_revoke_outcome(&mut buf, &revoke, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("unknown"));
assert_eq!(
v["tx"],
serde_json::json!(format!("{:#x}", B256::repeat_byte(0x55)))
);
}
#[test]
fn namespace_status_labels_and_hashes_match_their_states() {
let hash = B256::repeat_byte(0x55);
assert_eq!(NamespaceStatus::DryRun.label(), "dry_run");
assert_eq!(NamespaceStatus::DryRun.tx(), None);
assert_eq!(NamespaceStatus::DryRun.namespace_id(), None);
assert_eq!(NamespaceStatus::Failed.label(), "failed");
assert_eq!(NamespaceStatus::Failed.tx(), None);
assert_eq!(NamespaceStatus::Failed.namespace_id(), None);
assert_eq!(NamespaceStatus::InFlight(hash).label(), "unknown");
assert_eq!(NamespaceStatus::InFlight(hash).tx(), Some(hash));
assert_eq!(NamespaceStatus::InFlight(hash).namespace_id(), None);
let created = NamespaceStatus::Created {
tx: hash,
id: Some(9),
};
assert_eq!(created.label(), "created");
assert_eq!(created.tx(), Some(hash));
assert_eq!(created.namespace_id(), Some(9));
let created_no_id = NamespaceStatus::Created { tx: hash, id: None };
assert_eq!(created_no_id.label(), "created");
assert_eq!(created_no_id.tx(), Some(hash));
assert_eq!(created_no_id.namespace_id(), None);
}
#[test]
fn revoke_status_labels_and_hashes_match_their_states() {
let hash = B256::repeat_byte(0x55);
assert_eq!(RevokeStatus::DryRun.label(), "dry_run");
assert_eq!(RevokeStatus::DryRun.tx(), None);
assert_eq!(RevokeStatus::Failed.label(), "failed");
assert_eq!(RevokeStatus::Failed.tx(), None);
assert_eq!(RevokeStatus::InFlight(hash).label(), "unknown");
assert_eq!(RevokeStatus::InFlight(hash).tx(), Some(hash));
assert_eq!(RevokeStatus::Revoked(hash).label(), "revoked");
assert_eq!(RevokeStatus::Revoked(hash).tx(), Some(hash));
assert_eq!(RevokeStatus::Reverted(hash).label(), "reverted");
assert_eq!(RevokeStatus::Reverted(hash).tx(), Some(hash));
assert_eq!(RevokeStatus::Reverted(hash).submitted(), Some(true));
assert_eq!(RevokeStatus::MaybeBroadcast.label(), "maybe_broadcast");
assert_eq!(RevokeStatus::MaybeBroadcast.tx(), None);
assert_eq!(RevokeStatus::MaybeBroadcast.submitted(), None);
}
#[test]
fn revoke_json_round_trip() {
let revoke = RevokeOutcome {
operator: None,
origin_assignment: Address::repeat_byte(0x02),
namespace_id: 7,
revoked: Address::repeat_byte(0x11),
status: RevokeStatus::DryRun,
};
let mut buf = Vec::new();
write_revoke_outcome(&mut buf, &revoke, true).unwrap();
let v: serde_json::Value = serde_json::from_slice(&buf).unwrap();
assert_eq!(v["status"], serde_json::json!("dry_run"));
assert_eq!(v["submitted"], serde_json::json!(false));
assert!(v["tx"].is_null(), "{v}");
assert_eq!(
v["revoked_operator"],
serde_json::json!(format!("{:#x}", revoke.revoked))
);
}
#[test]
fn unique_operators_accepts_distinct_rejects_dupes() {
let a = Address::repeat_byte(0x11);
let b = Address::repeat_byte(0x22);
assert!(ensure_unique_operators(&[a, b]).is_ok());
let err = ensure_unique_operators(&[a, b, a]).unwrap_err().to_string();
assert!(err.contains("duplicate operator address"), "{err}");
assert!(err.contains(&format!("{a:#x}")), "{err}");
}
#[test]
fn chain_id_guard_matches_and_mismatches() {
assert!(chain_id_guard(421_614, 421_614).is_ok());
let err = chain_id_guard(421_614, 31_337).unwrap_err().to_string();
assert!(err.contains("421614"), "{err}");
assert!(err.contains("31337"), "{err}");
}