Skip to content
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

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

154 changes: 154 additions & 0 deletions crates/astria-core/src/protocol/bridge/v1alpha1/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
use super::raw;
use crate::primitive::v1::{
asset,
asset::denom::ParseDenomError,
Address,
AddressError,
IncorrectRollupIdLength,
RollupId,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeAccountLastTxHashResponse {
Expand Down Expand Up @@ -76,3 +84,149 @@ enum BridgeAccountLastTxHashResponseErrorKind {
#[error("invalid tx hash; must be 32 bytes, got {0} bytes")]
InvalidTxHash(usize),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeAccountInfoResponse {
pub height: u64,
pub info: Option<BridgeAccountInfo>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeAccountInfo {
pub rollup_id: RollupId,
pub asset: asset::Denom,
pub sudo_address: Address,
pub withdrawer_address: Address,
}

impl BridgeAccountInfoResponse {
/// Converts a protobuf [`raw::BridgeAccountInfoResponse`] to a native
/// [`BridgeAccountInfoResponse`].
///
/// # Errors
///
/// - if the `rollup_id` field is set but the `sudo_address` field is not
/// - if the `rollup_id` field is set but the `withdrawer_address` field is not
/// - if the `rollup_id` field is set but the `asset_id` field is not
/// - if the `asset` field does not contain a valid asset denom
/// - if the `rollup_id` field is set but invalid
/// - if the `sudo_address` field is set but invalid
/// - if the `withdrawer_address` field is set but invalid
pub fn try_from_raw(
raw: raw::BridgeAccountInfoResponse,
) -> Result<Self, BridgeAccountInfoResponseError> {
let raw::BridgeAccountInfoResponse {
height,
rollup_id,
asset,
sudo_address,
withdrawer_address,
} = raw;

let Some(rollup_id) = rollup_id else {
return Ok(Self {
height,
info: None,
});
};

let Some(sudo_address) = sudo_address else {
return Err(BridgeAccountInfoResponseError::field_not_set(
"sudo_address",
));
};

let Some(withdrawer_address) = withdrawer_address else {
return Err(BridgeAccountInfoResponseError::field_not_set(
"withdrawer_address",
));
};

let Some(asset) = asset else {
return Err(BridgeAccountInfoResponseError::field_not_set("asset"));
};

let asset = asset
.parse()
.map_err(BridgeAccountInfoResponseError::invalid_denom)?;

Ok(Self {
height,
info: Some(BridgeAccountInfo {
rollup_id: RollupId::try_from_raw(&rollup_id)
.map_err(BridgeAccountInfoResponseError::invalid_rollup_id)?,
asset,
sudo_address: Address::try_from_raw(&sudo_address)
.map_err(BridgeAccountInfoResponseError::invalid_sudo_address)?,
withdrawer_address: Address::try_from_raw(&withdrawer_address)
.map_err(BridgeAccountInfoResponseError::invalid_withdrawer_address)?,
}),
})
}

#[must_use]
pub fn into_raw(self) -> raw::BridgeAccountInfoResponse {
let Some(info) = self.info else {
return raw::BridgeAccountInfoResponse {
height: self.height,
rollup_id: None,
asset: None,
sudo_address: None,
withdrawer_address: None,
};
};

raw::BridgeAccountInfoResponse {
height: self.height,
rollup_id: Some(info.rollup_id.into_raw()),
asset: Some(info.asset.to_string()),
sudo_address: Some(info.sudo_address.into_raw()),
withdrawer_address: Some(info.withdrawer_address.into_raw()),
}
}
}

#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct BridgeAccountInfoResponseError(BridgeAccountInfoResponseErrorKind);

#[derive(Debug, thiserror::Error)]
enum BridgeAccountInfoResponseErrorKind {
#[error("the expected field in the raw source type was not set: `{0}`")]
FieldNotSet(&'static str),
#[error("the `denom` field was invalid")]
InvalidDenom(#[source] ParseDenomError),
#[error("the `rollup_id` field was invalid")]
InvalidRollupId(#[source] IncorrectRollupIdLength),
#[error("the `sudo_address` field was invalid")]
InvalidSudoAddress(#[source] AddressError),
#[error("the `withdrawer_address` field was invalid")]
InvalidWithdrawerAddress(#[source] AddressError),
}

impl BridgeAccountInfoResponseError {
#[must_use]
pub fn field_not_set(field: &'static str) -> Self {
Self(BridgeAccountInfoResponseErrorKind::FieldNotSet(field))
}

#[must_use]
pub fn invalid_rollup_id(err: IncorrectRollupIdLength) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidRollupId(err))
}

#[must_use]
pub fn invalid_sudo_address(err: AddressError) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidSudoAddress(err))
}

#[must_use]
pub fn invalid_withdrawer_address(err: AddressError) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidWithdrawerAddress(err))
}

#[must_use]
pub fn invalid_denom(err: ParseDenomError) -> Self {
Self(BridgeAccountInfoResponseErrorKind::InvalidDenom(err))
}
}
37 changes: 36 additions & 1 deletion crates/astria-sequencer-client/src/extension_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ use std::{

use astria_core::protocol::{
asset::v1alpha1::AllowedFeeAssetsResponse,
bridge::v1alpha1::BridgeAccountLastTxHashResponse,
bridge::v1alpha1::{
BridgeAccountInfoResponse,
BridgeAccountLastTxHashResponse,
},
};
pub use astria_core::{
primitive::v1::Address,
Expand Down Expand Up @@ -543,6 +546,38 @@ pub trait SequencerClientExt: Client {
self.get_nonce(address, 0u32).await
}

async fn get_bridge_account_info(
&self,
address: Address,
) -> Result<BridgeAccountInfoResponse, Error> {
const PREFIX: &str = "bridge/account_info";
let path = format!("{PREFIX}/{address}");

let response = self
.abci_query(Some(path), vec![], None, false)
.await
.map_err(|e| Error::tendermint_rpc("abci_query", e))?;

let proto_response =
astria_core::generated::protocol::bridge::v1alpha1::BridgeAccountInfoResponse::decode(
&*response.value,
)
.map_err(|e| {
Error::abci_query_deserialization(
"astria.protocol.bridge.v1alpha1.BridgeAccountInfoResponse",
response,
e,
)
})?;
let native = BridgeAccountInfoResponse::try_from_raw(proto_response).map_err(|e| {
Error::native_conversion(
"astria.protocol.bridge.v1alpha1.BridgeAccountInfoResponse",
Arc::new(e),
)
})?;
Ok(native)
}

async fn get_bridge_account_last_transaction_hash(
&self,
address: Address,
Expand Down
33 changes: 33 additions & 0 deletions crates/astria-sequencer-client/src/tests/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,39 @@ async fn get_allowed_fee_assets() {
assert_eq!(expected_response, actual_response);
}

#[tokio::test]
async fn get_bridge_account_info() {
use astria_core::{
generated::protocol::bridge::v1alpha1::BridgeAccountInfoResponse,
primitive::v1::RollupId,
};

let MockSequencer {
server,
client,
} = MockSequencer::start().await;

let expected_response = BridgeAccountInfoResponse {
height: 10,
rollup_id: Some(RollupId::from_unhashed_bytes(b"rollup_0").into_raw()),
asset: Some("asset_0".parse().unwrap()),
sudo_address: Some(alice_address().into_raw()),
withdrawer_address: Some(alice_address().into_raw()),
};

let _guard =
register_abci_query_response(&server, "bridge/account_info", expected_response.clone())
.await;

let actual_response = client
.get_bridge_account_info(alice_address())
.await
.unwrap()
.into_raw();

assert_eq!(expected_response, actual_response);
}

#[tokio::test]
async fn get_bridge_account_last_transaction_hash() {
use astria_core::generated::protocol::bridge::v1alpha1::BridgeAccountLastTxHashResponse;
Expand Down
Loading