Skip to content

Add tests for observation #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ tokio = "1.45.1"
tokio-stream = "0.1.17"
tracing = "0.1.41"
wormhole-vaas-serde = "0.1.0"

[dev-dependencies]
serde_json = "1.0.140"
88 changes: 88 additions & 0 deletions src/api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,91 @@ impl ApiClient {
}
}
}

#[cfg(test)]
mod tests {
use secp256k1::{
ecdsa::{RecoverableSignature, RecoveryId},
PublicKey,
};
use serde_json::Value;
use serde_wormhole::RawMessage;
use wormhole_sdk::{Address, Chain};

use super::*;

#[test]
fn test_new_signed_observation() {
let secret_key = SecretKey::from_byte_array([1u8; 32]).expect("Invalid secret key length");
let body = Body {
timestamp: 1234567890,
nonce: 42,
emitter_chain: Chain::Solana,
emitter_address: Address([1u8; 32]),
sequence: 1000,
consistency_level: 1,
payload: vec![1, 2, 3, 4, 5],
};
let observation =
Observation::try_new(body.clone(), secret_key).expect("Failed to create observation");
assert_eq!(observation.version, 1);
assert_eq!(observation.body, body);

// Signature verification
let secp = Secp256k1::new();
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)
.try_into()
.expect("Invalid recovery ID");
let recoverable_sig =
RecoverableSignature::from_compact(&observation.signature[..64], recovery_id)
.expect("Invalid recoverable signature");

let pubkey = secp
.recover_ecdsa(message, &recoverable_sig)
.expect("Failed to recover pubkey");

let expected_pubkey = PublicKey::from_secret_key(&secp, &secret_key);
assert_eq!(pubkey, expected_pubkey);
}

#[test]
fn test_observation_serialization() {
let payload = vec![5, 1, 2, 3, 4, 5];
let observation = Observation {
version: 1,
signature: [1u8; 65],
body: Body {
timestamp: 1234567890,
nonce: 42,
emitter_chain: Chain::Solana,
emitter_address: Address([1u8; 32]),
sequence: 1000,
consistency_level: 1,
payload: RawMessage::new(payload.as_slice()),
},
};

let serialized =
serde_json::to_string(&observation).expect("Failed to serialize observation");
let parsed: Value = serde_json::from_str(&serialized).expect("Failed to parse JSON");

assert_eq!(parsed["version"], 1);
let sig = parsed["signature"]
.as_str()
.expect("Signature should be a string");
let decoded = hex::decode(sig).expect("Should be valid hex");
assert_eq!(decoded, observation.signature);

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)
.collect::<Vec<u8>>();
let deserialized: Body<&RawMessage> =
serde_wormhole::from_slice(&bytes).expect("Failed to deserialize body");
assert_eq!(deserialized, observation.body);
}
}
34 changes: 32 additions & 2 deletions src/posted_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use {
},
};

#[derive(Default, Debug, Clone)]
#[derive(Default, Debug, Clone, PartialEq)]
pub struct PostedMessageUnreliableData {
pub message: MessageData,
}

#[derive(Debug, Default, BorshSerialize, BorshDeserialize, Clone, Serialize, Deserialize)]
#[derive(
Debug, Default, BorshSerialize, BorshDeserialize, Clone, Serialize, Deserialize, PartialEq,
)]
pub struct MessageData {
/// Header of the posted VAA
pub vaa_version: u8,
Expand Down Expand Up @@ -89,3 +91,31 @@ impl DerefMut for PostedMessageUnreliableData {
&mut self.message
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_borsh_roundtrip() {
let post_message_unreliable_data = PostedMessageUnreliableData {
message: MessageData {
vaa_version: 1,
consistency_level: 2,
vaa_time: 3,
vaa_signature_account: [4u8; 32],
submission_time: 5,
nonce: 6,
sequence: 7,
emitter_chain: 8,
emitter_address: [9u8; 32],
payload: vec![10u8; 32],
},
};

let encoded = borsh::to_vec(&post_message_unreliable_data).unwrap();

let decoded = PostedMessageUnreliableData::try_from_slice(encoded.as_slice()).unwrap();
assert_eq!(decoded, post_message_unreliable_data);
}
}
Loading