1use babylon_bitcoin::{deserialize, Transaction};
2
3use cosmwasm_std::StdError;
4
5use crate::btc_staking_api::{
6 ActiveBtcDelegation, FinalityProviderDescription, NewFinalityProvider, ProofOfPossessionBtc,
7 SlashedBtcDelegation, UnbondedBtcDelegation, HASH_SIZE,
8};
9use crate::error::StakingApiError;
10
11pub trait Validate {
13 fn validate(&self) -> Result<(), StakingApiError>;
14}
15
16impl Validate for NewFinalityProvider {
17 fn validate(&self) -> Result<(), StakingApiError> {
18 self.description
19 .as_ref()
20 .map(FinalityProviderDescription::validate)
21 .transpose()?;
22
23 if self.btc_pk_hex.is_empty() {
24 return Err(StakingApiError::EmptyBtcPk);
25 }
26
27 let _btc_pk = hex::decode(&self.btc_pk_hex)?;
28
29 match self.pop {
34 Some(ref pop) => pop.validate()?,
35 None => return Err(StakingApiError::MissingPop),
36 }
37
38 if self.consumer_id.is_empty() {
40 return Err(StakingApiError::EmptyChainId);
41 }
42
43 Ok(())
44 }
45}
46
47impl Validate for FinalityProviderDescription {
48 fn validate(&self) -> Result<(), StakingApiError> {
49 if self.moniker.is_empty() {
50 return Err(StakingApiError::description_err("Moniker cannot be empty"));
51 }
52 if self.moniker.len() > FinalityProviderDescription::MAX_MONIKER_LENGTH {
53 return Err(StakingApiError::description_err(format!(
54 "Invalid moniker length; got: {}, max: {}",
55 self.moniker.len(),
56 FinalityProviderDescription::MAX_MONIKER_LENGTH
57 )));
58 }
59
60 if self.identity.len() > FinalityProviderDescription::MAX_IDENTITY_LENGTH {
61 return Err(StakingApiError::from(StdError::generic_err(format!(
62 "Invalid identity length; got: {}, max: {}",
63 self.identity.len(),
64 FinalityProviderDescription::MAX_IDENTITY_LENGTH
65 ))));
66 }
67
68 if self.website.len() > FinalityProviderDescription::MAX_WEBSITE_LENGTH {
69 return Err(StakingApiError::from(StdError::generic_err(format!(
70 "Invalid website length; got: {}, max: {}",
71 self.website.len(),
72 FinalityProviderDescription::MAX_WEBSITE_LENGTH
73 ))));
74 }
75
76 if self.security_contact.len() > FinalityProviderDescription::MAX_SECURITY_CONTACT_LENGTH {
77 return Err(StakingApiError::from(StdError::generic_err(format!(
78 "Invalid security contact length; got: {}, max: {}",
79 self.security_contact.len(),
80 FinalityProviderDescription::MAX_SECURITY_CONTACT_LENGTH
81 ))));
82 }
83
84 if self.details.len() > FinalityProviderDescription::MAX_DETAILS_LENGTH {
85 return Err(StakingApiError::from(StdError::generic_err(format!(
86 "Invalid details length; got: {}, max: {}",
87 self.details.len(),
88 FinalityProviderDescription::MAX_DETAILS_LENGTH
89 ))));
90 }
91
92 Ok(())
93 }
94}
95
96impl Validate for ProofOfPossessionBtc {
97 fn validate(&self) -> Result<(), StakingApiError> {
99 Ok(())
100 }
101}
102
103impl Validate for ActiveBtcDelegation {
104 fn validate(&self) -> Result<(), StakingApiError> {
105 fn check_duplicated_fps(del: &ActiveBtcDelegation) -> Result<(), StakingApiError> {
106 let mut fp_btc_pk_set = std::collections::HashSet::new();
107 for fp_btc_pk in &del.fp_btc_pk_list {
108 if !fp_btc_pk_set.insert(fp_btc_pk) {
109 return Err(StakingApiError::DuplicatedBtcPk(fp_btc_pk.clone()));
110 }
111 }
112 Ok(())
113 }
114
115 if self.btc_pk_hex.is_empty() {
116 return Err(StakingApiError::EmptyBtcPk);
117 }
118 if self.staking_tx.is_empty() {
119 return Err(StakingApiError::EmptyStakingTx);
120 }
121 if self.slashing_tx.is_empty() {
122 return Err(StakingApiError::EmptySlashingTx);
123 }
124 let _: Transaction = deserialize(&self.slashing_tx)
125 .map_err(|_| StakingApiError::InvalidBtcTx(hex::encode(&self.slashing_tx)))?;
126
127 if self.fp_btc_pk_list.is_empty() {
131 return Err(StakingApiError::EmptyBtcPkList);
132 }
133 check_duplicated_fps(self)?;
135
136 if self.unbonding_time > u16::MAX as u32 {
139 return Err(StakingApiError::ErrInvalidUnbondingTime(
140 self.unbonding_time,
141 u16::MAX as u32,
142 ));
143 }
144
145 let undelegation_info = &self.undelegation_info;
146 if undelegation_info.unbonding_tx.is_empty() {
148 return Err(StakingApiError::EmptyUnbondingTx);
149 }
150
151 if undelegation_info.slashing_tx.is_empty() {
153 return Err(StakingApiError::EmptySlashingTx);
154 }
155
156 if undelegation_info.delegator_slashing_sig.is_empty() {
158 return Err(StakingApiError::EmptySignature);
159 }
160
161 Ok(())
162 }
163}
164
165impl Validate for UnbondedBtcDelegation {
166 fn validate(&self) -> Result<(), StakingApiError> {
167 if self.staking_tx_hash.len() != HASH_SIZE * 2 {
168 return Err(StakingApiError::InvalidStakingTxHash(HASH_SIZE * 2));
169 }
170
171 if self.unbonding_tx_sig.is_empty() {
172 return Err(StakingApiError::EmptySignature);
173 }
174
175 Ok(())
178 }
179}
180
181impl Validate for SlashedBtcDelegation {
182 fn validate(&self) -> Result<(), StakingApiError> {
183 if self.staking_tx_hash.len() != HASH_SIZE * 2 {
184 return Err(StakingApiError::InvalidStakingTxHash(HASH_SIZE * 2));
185 }
186
187 Ok(())
192 }
193}