diff --git a/Cargo.toml b/Cargo.toml index 9570178..b5cb61d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,45 @@ mockall = "0.13.1" [profile.release] overflow-checks = true + +[lints.rust] +unsafe_code = "deny" + +[lints.clippy] +# See https://github.com/pyth-network/pyth-crosschain/blob/main/doc/rust-code-guidelines.md + +wildcard_dependencies = "deny" + +collapsible_if = "allow" +collapsible_else_if = "allow" + +allow_attributes_without_reason = "warn" + +# Panics +expect_used = "warn" +fallible_impl_from = "warn" +indexing_slicing = "warn" +panic = "warn" +panic_in_result_fn = "warn" +string_slice = "warn" +todo = "warn" +unchecked_duration_subtraction = "warn" +unreachable = "warn" +unwrap_in_result = "warn" +unwrap_used = "warn" + +# Correctness +cast_lossless = "warn" +cast_possible_truncation = "warn" +cast_possible_wrap = "warn" +cast_sign_loss = "warn" +collection_is_never_read = "warn" +match_wild_err_arm = "warn" +path_buf_push_overwrite = "warn" +read_zero_byte_vec = "warn" +same_name_method = "warn" +suspicious_operation_groupings = "warn" +suspicious_xor_used_as_pow = "warn" +unused_self = "warn" +used_underscore_binding = "warn" +while_float = "warn" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..4d90be6 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true +allow-indexing-slicing-in-tests = true +allow-panic-in-tests = true diff --git a/src/api_client.rs b/src/api_client.rs index 6258d9f..1b5d698 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -148,7 +148,7 @@ mod tests { let digest = body.digest().expect("Failed to compute digest"); let message = Message::from_digest(digest.secp256k_hash); - let recovery_id: RecoveryId = (observation.signature[64] as i32) + let recovery_id: RecoveryId = i32::from(observation.signature[64]) .try_into() .expect("Invalid recovery ID"); let recoverable_sig = @@ -194,7 +194,10 @@ mod tests { let message = parsed["body"].as_array().expect("Body should be an array"); let bytes = message .iter() - .map(|v| v.as_u64().expect("Body elements should be u64") as u8) + .map(|v| { + u8::try_from(v.as_u64().expect("Body elements should be u64")) + .expect("Failed to convert to u8") + }) .collect::>(); let deserialized: Body<&RawMessage> = serde_wormhole::from_slice(&bytes).expect("Failed to deserialize body"); diff --git a/src/main.rs b/src/main.rs index de2b615..a390131 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use { config::Command, signer::{GuardianKey, Signer, GUARDIAN_KEY_ARMORED_BLOCK, STANDARD_ARMOR_LINE_HEADER}, }, + anyhow::Context, api_client::{ApiClient, Observation}, borsh::BorshDeserialize, clap::Parser, @@ -215,27 +216,27 @@ async fn get_signer(run_options: config::RunOptions) -> anyhow::Result anyhow::Result<()> { let signer = get_signer(run_options.clone()) .await - .expect("Failed to create signer"); + .context("Failed to create signer")?; let client = PubsubClient::new(&run_options.pythnet_url) .await - .expect("Invalid WebSocket URL"); + .context("Invalid WebSocket URL")?; drop(client); // Drop the client to avoid holding the connection open let accumulator_address = Pubkey::from_str("G9LV2mp9ua1znRAfYwZz5cPiJMAbo1T6mbjdQsDZuMJg") - .expect("Invalid accumulator address"); + .context("Invalid accumulator address")?; let wormhole_pid = - Pubkey::from_str(&run_options.wormhole_pid).expect("Invalid Wormhole program ID"); + Pubkey::from_str(&run_options.wormhole_pid).context("Invalid Wormhole program ID")?; let api_clients: Vec = run_options .server_urls .into_iter() - .map(|server_url| { - ApiClient::try_new(server_url, None).expect("Failed to create API client") - }) - .collect(); + .map(|server_url| ApiClient::try_new(server_url, None)) + .collect::>>()?; - let (pubkey, pubkey_evm) = signer.get_public_key().expect("Failed to get public key"); + let (pubkey, pubkey_evm) = signer + .get_public_key() + .context("Failed to get public key")?; let evm_encded_public_key = format!("0x{}", hex::encode(pubkey_evm)); tracing::info!( public_key = ?pubkey, @@ -260,7 +261,7 @@ async fn run(run_options: config::RunOptions) { } #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { // Initialize a Tracing Subscriber let fmt_builder = tracing_subscriber::fmt() .with_file(false) @@ -272,16 +273,16 @@ async fn main() { // Use the compact formatter if we're in a terminal, otherwise use the JSON formatter. if std::io::stderr().is_terminal() { tracing::subscriber::set_global_default(fmt_builder.compact().finish()) - .expect("Failed to set global default subscriber"); + .context("Failed to set global default subscriber")?; } else { tracing::subscriber::set_global_default(fmt_builder.json().finish()) - .expect("Failed to set global default subscriber"); + .context("Failed to set global default subscriber")?; } // Parse the command line arguments with StructOpt, will exit automatically on `--help` or // with invalid arguments. match Command::parse() { - Command::Run(run_options) => run(run_options).await, + Command::Run(run_options) => run(run_options).await?, Command::GenerateKey(opts) => { let secp = Secp256k1::new(); let mut rng = OsRng; @@ -289,7 +290,9 @@ async fn main() { // Generate keypair (secret + public key) let (secret_key, _) = secp.generate_keypair(&mut rng); let signer = signer::FileSigner { secret_key }; - let (pubkey, pubkey_evm) = signer.get_public_key().expect("Failed to get public key"); + let (pubkey, pubkey_evm) = signer + .get_public_key() + .context("Failed to get public key")?; let guardian_key = GuardianKey { data: secret_key.secret_bytes().to_vec(), @@ -300,24 +303,25 @@ async fn main() { Kind::SecretKey, vec![("PublicKey", format!("0x{}", hex::encode(pubkey_evm)))], ) - .expect("Failed to create writer"); + .context("Failed to create writer")?; writer .write_all(guardian_key.encode_to_vec().as_slice()) - .expect("Failed to write GuardianKey to writer"); - let buffer = writer.finalize().expect("Failed to finalize writer"); + .context("Failed to write GuardianKey to writer")?; + let buffer = writer.finalize().context("Failed to finalize writer")?; let armored_string = - String::from_utf8(buffer).expect("Failed to convert buffer to string"); + String::from_utf8(buffer).context("Failed to convert buffer to string")?; let armored_string = armored_string.replace(STANDARD_ARMOR_LINE_HEADER, GUARDIAN_KEY_ARMORED_BLOCK); - fs::write(&opts.output_path, armored_string) - .expect("Failed to write GuardianKey to file"); + fs::write(&opts.output_path, armored_string).context("Failed to write key to file")?; tracing::info!("Generated secret key at: {}", opts.output_path); tracing::info!("Public key: {}", pubkey); tracing::info!("EVM encoded public key: 0x{}", hex::encode(pubkey_evm)); } } + + Ok(()) } #[cfg(test)] diff --git a/src/posted_message.rs b/src/posted_message.rs index 92bffdc..a623f32 100644 --- a/src/posted_message.rs +++ b/src/posted_message.rs @@ -70,7 +70,9 @@ impl BorshDeserialize for PostedMessageUnreliableData { } let expected = b"msu"; - let magic: &[u8] = &buf[0..3]; + let magic: &[u8] = buf + .get(0..3) + .ok_or(Error::new(InvalidData, "Failed to get magic bytes"))?; if magic != expected { return Err(Error::new( InvalidData, @@ -80,7 +82,9 @@ impl BorshDeserialize for PostedMessageUnreliableData { ), )); }; - *buf = &buf[3..]; + *buf = buf + .get(3..) + .ok_or(Error::new(InvalidData, "Failed to get remaining bytes"))?; Ok(PostedMessageUnreliableData { message: ::deserialize(buf)?, }) diff --git a/src/signer.rs b/src/signer.rs index 18e6677..87a9c14 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -87,7 +87,11 @@ fn get_evm_address(public_key: &PublicKey) -> anyhow::Result<[u8; 20]> { let pubkey_hash: [u8; 32] = Keccak256::new_with_prefix(&pubkey_uncompressed[1..]) .finalize() .into(); - let pubkey_evm: [u8; 20] = pubkey_hash[pubkey_hash.len() - 20..] + let pubkey_evm: [u8; 20] = pubkey_hash + .get(pubkey_hash.len() - 20..) + .ok_or(anyhow::anyhow!( + "Failed to get EVM address from public key hash" + ))? .try_into() .map_err(|e| anyhow::anyhow!("Failed to convert public key hash to EVM format: {}", e))?; Ok(pubkey_evm) @@ -102,7 +106,7 @@ impl Signer for FileSigner { let recovery_id: i32 = recovery_id.into(); let mut signature = [0u8; 65]; signature[..64].copy_from_slice(&signature_bytes); - signature[64] = recovery_id as u8; + signature[64] = u8::try_from(recovery_id)?; Ok(signature) } @@ -220,7 +224,7 @@ impl Signer for KMSSigner { if recovered_public_key == public_key.0 { let mut signature = [0u8; 65]; signature[..64].copy_from_slice(&signature_bytes); - signature[64] = raw_id as u8; + signature[64] = u8::try_from(raw_id)?; return Ok(signature); } }