use super::*;
fn error_resp(code: i64, message: &str) -> ContractError {
ContractError::TransportError(alloy::transports::RpcError::ErrorResp(
serde_json::from_value(serde_json::json!({ "code": code, "message": message })).unwrap(),
))
}
#[test]
fn only_a_nonce_rejection_is_a_nonce_collision() {
assert!(super::is_nonce_collision(&error_resp(
-32003,
"nonce too low"
)));
assert!(super::is_nonce_collision(&error_resp(
-32000,
"Nonce too low: next nonce 7, tx nonce 6"
)));
assert!(super::is_nonce_collision(&error_resp(
-32000,
"replacement transaction underpriced"
)));
assert!(!super::is_nonce_collision(&error_resp(
3,
"execution reverted"
)));
assert!(!super::is_nonce_collision(&error_resp(
-32000,
"insufficient funds for gas"
)));
assert!(!super::is_nonce_collision(&ContractError::TransportError(
alloy::transports::TransportErrorKind::custom_str("nonce too low")
)));
}
#[test]
fn send_outcome_hash_and_effect_are_total() {
let h = B256::repeat_byte(0x55);
assert_eq!(SendOutcome::Confirmed(h).tx(), Some(h));
assert_eq!(SendOutcome::Reverted(h).tx(), Some(h));
assert_eq!(SendOutcome::InFlight(h).tx(), Some(h));
assert_eq!(SendOutcome::MaybeBroadcast.tx(), None);
assert_eq!(SendOutcome::Rejected.tx(), None);
assert_eq!(SendOutcome::NotSent.tx(), None);
assert!(SendOutcome::Confirmed(h).maybe_effected());
assert!(SendOutcome::InFlight(h).maybe_effected());
assert!(SendOutcome::MaybeBroadcast.maybe_effected());
assert!(!SendOutcome::Reverted(h).maybe_effected());
assert!(!SendOutcome::Rejected.maybe_effected());
assert!(!SendOutcome::NotSent.maybe_effected());
}
fn http_error(status: u16) -> ContractError {
ContractError::TransportError(alloy::transports::RpcError::Transport(
alloy::transports::TransportErrorKind::HttpError(alloy::transports::HttpError {
status,
body: String::new(),
}),
))
}
#[test]
fn a_rejection_is_not_a_broadcast() {
assert!(!send_broadcast_unknown(&error_resp(
3,
"execution reverted"
)));
assert!(!send_broadcast_unknown(&error_resp(
-32000,
"nonce too low"
)));
assert!(!send_broadcast_unknown(&ContractError::TransportError(
alloy::transports::RpcError::UnsupportedFeature("batching")
)));
assert!(!send_broadcast_unknown(&ContractError::ContractNotDeployed));
assert!(!send_broadcast_unknown(&http_error(401)));
assert!(!send_broadcast_unknown(&http_error(404)));
}
#[test]
fn a_transport_failure_leaves_the_broadcast_unknown() {
let transport = ContractError::TransportError(
alloy::transports::TransportErrorKind::custom_str("connection reset"),
);
assert!(send_broadcast_unknown(&transport));
assert!(send_broadcast_unknown(&ContractError::TransportError(
alloy::transports::RpcError::NullResp
)));
assert!(send_broadcast_unknown(&http_error(502)));
assert!(send_broadcast_unknown(&http_error(500)));
}
#[tokio::test(start_paused = true)]
async fn a_receipt_that_never_arrives_lapses_in_flight() {
use crate::test_rpc::{SCRIPTED_TX, ScriptedRpc};
let provider = ScriptedRpc::parked_receipt().provider();
let call = CallBuilder::new_raw(&provider, alloy::primitives::Bytes::new())
.to(alloy::primitives::Address::repeat_byte(0x01));
let mut outcome = SendOutcome::NotSent;
let sent = tokio::time::timeout(
RECEIPT_TIMEOUT * 2,
send_for_receipt(call, "closePool", None, &mut outcome),
)
.await
.expect("the receipt wait must lapse within RECEIPT_TIMEOUT");
let err = sent.unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains(&format!("{SCRIPTED_TX:#x}")), "{msg}");
assert!(msg.contains("it may still mine"), "{msg}");
assert!(msg.contains("no receipt within"), "{msg}");
assert_eq!(outcome, SendOutcome::InFlight(SCRIPTED_TX));
}
#[tokio::test(start_paused = true)]
async fn an_unbounded_receipt_wait_keeps_waiting() {
use crate::test_rpc::ScriptedRpc;
let provider = ScriptedRpc::parked_receipt().provider();
let pending = CallBuilder::new_raw(&provider, alloy::primitives::Bytes::new())
.to(alloy::primitives::Address::repeat_byte(0x01))
.send()
.await
.unwrap();
let waited = tokio::time::timeout(RECEIPT_TIMEOUT * 10, await_receipt(pending, None)).await;
assert!(waited.is_err(), "an unbounded wait must still be pending");
}