Skip to content

Commit

Permalink
Add check on native warp route and existence of account
Browse files Browse the repository at this point in the history
  • Loading branch information
ameten committed Feb 12, 2025
1 parent 0a212e5 commit a7b602d
Show file tree
Hide file tree
Showing 2 changed files with 280 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use std::collections::HashSet;
use std::future::Future;
use std::io::Cursor;

use async_trait::async_trait;
use derive_new::new;
use lazy_static::lazy_static;
use solana_sdk::pubkey::Pubkey;
use tracing::debug;

use hyperlane_core::{Decode, HyperlaneMessage, U256};
use hyperlane_core::{
utils::hex_or_base58_to_h256, ChainResult, Decode, HyperlaneMessage, H256, U256,
};
use hyperlane_operation_verifier::{
ApplicationOperationVerifier, ApplicationOperationVerifierReport,
};
Expand All @@ -13,6 +19,16 @@ use hyperlane_warp_route::TokenMessage;
use crate::SealevelProvider;

const WARP_ROUTE_PREFIX: &str = "SOL/";
// Warp routes in Solana mainnet side
lazy_static! {
static ref NATIVE_WARP_ROUTES: HashSet<H256> = {
HashSet::from([
hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(),
hex_or_base58_to_h256("BXKDfnNkgUNVT5uCfk36sv2GDtK6RwAt9SLbGiKzZkih").unwrap(),
hex_or_base58_to_h256("GPFwRQ5Cw6dTWnmappUKJt76DD8yawxPx28QugfCaGaA").unwrap(),
])
};
}

/// Application operation verifier for Sealevel
#[derive(new)]
Expand All @@ -27,46 +43,101 @@ impl ApplicationOperationVerifier for SealevelApplicationOperationVerifier {
app_context: &Option<String>,
message: &HyperlaneMessage,
) -> Option<ApplicationOperationVerifierReport> {
use ApplicationOperationVerifierReport::{AmountBelowMinimum, MalformedMessage};

debug!(
?app_context,
?message,
"Sealevel application operation verifier",
);

let context = match app_context {
Some(c) => c,
None => return None,
// Separate dependency on self and network invocations for ease of unit testing
let check_account_does_not_exist_and_get_minimum = |account: H256| async move {
let Ok(false) = self.account_exists(account).await else {
return None;
};

self.minimum().await
};

if !context.starts_with(WARP_ROUTE_PREFIX) {
return None;
}
Self::verify_message(
app_context,
message,
check_account_does_not_exist_and_get_minimum,
)
.await
}
}

impl SealevelApplicationOperationVerifier {
async fn verify_message<F, Fut>(
app_context: &Option<String>,
message: &HyperlaneMessage,
check_account_exists_and_get_minimum: F,
) -> Option<ApplicationOperationVerifierReport>
where
F: FnOnce(H256) -> Fut,
Fut: Future<Output = Option<U256>>,
{
use ApplicationOperationVerifierReport::{AmountBelowMinimum, MalformedMessage};

Self::verify_context(app_context)?;

// Starting from this point we assume that we are in a warp route context

NATIVE_WARP_ROUTES.get(&message.recipient)?;

let mut reader = Cursor::new(message.body.as_slice());
let token_message = match TokenMessage::read_from(&mut reader) {
Ok(m) => m,
Err(_) => return Some(MalformedMessage(message.clone())),
};

let minimum: U256 = match self
.provider
.rpc()
// We assume that account will contain no data
.get_minimum_balance_for_rent_exemption(0)
.await
{
Ok(m) => m.into(),
Err(_) => return None,
};
let minimum = check_account_exists_and_get_minimum(token_message.recipient()).await?;

if token_message.amount() < minimum {
return Some(AmountBelowMinimum(minimum, token_message.amount()));
}

None
}

fn verify_context(app_context: &Option<String>) -> Option<()> {
let context = match app_context {
Some(c) => c,
None => return None,
};

if !context.starts_with(WARP_ROUTE_PREFIX) {
return None;
}

Some(())
}

async fn minimum(&self) -> Option<U256> {
self.provider
.rpc()
// We assume that account will contain no data
.get_minimum_balance_for_rent_exemption(0)
.await
.ok()
.map(|v| v.into())
}

async fn account_exists(&self, address: H256) -> ChainResult<bool> {
let pubkey = Pubkey::from(<[u8; 32]>::from(address));

match self
.provider
.rpc()
.get_account_option_with_finalized_commitment(&pubkey)
.await
{
Ok(Some(_)) => Ok(true),
Ok(None) => Ok(false),
Err(e) => Err(e),
}
}
}

#[cfg(test)]
mod tests;
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use hyperlane_core::{utils::hex_or_base58_to_h256, Encode, HyperlaneMessage, H256, U256};
use hyperlane_operation_verifier::ApplicationOperationVerifierReport::{
AmountBelowMinimum, MalformedMessage,
};
use hyperlane_warp_route::TokenMessage;

use crate::application::SealevelApplicationOperationVerifier;

#[tokio::test]
async fn test_app_context_empty() {
// given
let app_context = None;
let message = HyperlaneMessage::default();
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert!(report.is_none());
}

#[tokio::test]
async fn test_app_context_not_warp_route() {
// given
let app_context = Some("not-warp-route".to_string());
let message = HyperlaneMessage::default();
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert!(report.is_none());
}

#[tokio::test]
async fn test_app_context_not_native_warp_route() {
// given
let app_context = Some("NOT_NATIVE/warp-route".to_string());
let message = HyperlaneMessage::default();
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert!(report.is_none());
}

#[tokio::test]
async fn test_message_not_native_warp_route_recipient() {
// given
let app_context = Some("SOL/warp-route".to_string());
let message = HyperlaneMessage {
recipient: hex_or_base58_to_h256("5dDyfdy9fannAdHEkYghgQpiPZrQPHadxBLa1WsGHPFi").unwrap(),
..Default::default()
};
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert!(report.is_none());
}

#[tokio::test]
async fn test_message_is_not_token_message() {
// given
let app_context = Some("SOL/warp-route".to_string());
let message = HyperlaneMessage {
recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(),
..Default::default()
};
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert_eq!(report.unwrap(), MalformedMessage(message));
}

#[tokio::test]
async fn test_token_recipient_exists_or_communication_error() {
// given
let app_context = Some("SOL/warp-route".to_string());
let token_message = TokenMessage::new(H256::zero(), U256::one(), vec![]);
let message = HyperlaneMessage {
recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(),
body: encode(token_message),
..Default::default()
};
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert!(report.is_none());
}

#[tokio::test]
async fn test_below_minimum() {
// given
let app_context = Some("SOL/warp-route".to_string());
let amount = U256::one();
let minimum = U256::one() * 2;
let token_message = TokenMessage::new(H256::zero(), amount, vec![]);
let message = HyperlaneMessage {
recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(),
body: encode(token_message),
..Default::default()
};
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { Some(minimum) };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert_eq!(report.unwrap(), AmountBelowMinimum(minimum, amount));
}

#[tokio::test]
async fn test_above_minimum() {
// given
let app_context = Some("SOL/warp-route".to_string());
let amount = U256::one() * 2;
let minimum = U256::one();
let token_message = TokenMessage::new(H256::zero(), amount, vec![]);
let message = HyperlaneMessage {
recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(),
body: encode(token_message),
..Default::default()
};
let check_account_does_not_exist_and_get_minimum = |_: H256| async move { Some(minimum) };

// when
let report = SealevelApplicationOperationVerifier::verify_message(
&app_context,
&message,
check_account_does_not_exist_and_get_minimum,
)
.await;

// then
assert!(report.is_none());
}

fn encode(token_message: TokenMessage) -> Vec<u8> {
let mut encoded = vec![];
token_message.write_to(&mut encoded).unwrap();
encoded
}

0 comments on commit a7b602d

Please sign in to comment.