1use crate::{account::state::ACCOUNT_ID, native_addrs, registry};
4use cosmwasm_std::{Addr, Deps, QuerierWrapper};
5use cw_address_like::AddressLike;
6use cw_utils::Expiration;
7
8use crate::AbstractError;
9
10use super::ownership::cw721;
11
12const MIN_GOV_TYPE_LENGTH: usize = 4;
13const MAX_GOV_TYPE_LENGTH: usize = 64;
14
15#[cosmwasm_schema::cw_serde]
17#[derive(Eq)]
18#[non_exhaustive]
19pub enum GovernanceDetails<T: AddressLike> {
20 Monarchy {
22 monarch: T,
24 },
25 SubAccount {
27 account: T,
29 },
30 External {
33 governance_address: T,
35 governance_type: String,
37 },
38 NFT {
41 collection_addr: T,
42 token_id: String,
43 },
44 AbstractAccount {
49 address: Addr,
51 },
52 Renounced {},
55}
56
57#[cosmwasm_schema::cw_serde]
59pub enum GovAction {
60 TransferOwnership {
67 new_owner: GovernanceDetails<String>,
68 expiry: Option<Expiration>,
69 },
70
71 AcceptOwnership,
75
76 RenounceOwnership,
83}
84
85impl GovernanceDetails<String> {
86 pub fn verify(self, deps: Deps) -> Result<GovernanceDetails<Addr>, AbstractError> {
88 match self {
89 GovernanceDetails::Monarchy { monarch } => {
90 let addr = deps.api.addr_validate(&monarch)?;
91 Ok(GovernanceDetails::Monarchy { monarch: addr })
92 }
93 GovernanceDetails::SubAccount { account } => {
94 let account_addr = deps.api.addr_validate(&account)?;
95
96 let abstract_code_id = native_addrs::abstract_code_id(&deps.querier, account)?;
97 let registry_address = native_addrs::registry_address(deps, abstract_code_id)?;
98 let registry_address = deps.api.addr_humanize(®istry_address)?;
99
100 let account_id = ACCOUNT_ID.query(&deps.querier, account_addr.clone())?;
101 let base = registry::state::ACCOUNT_ADDRESSES.query(
102 &deps.querier,
103 registry_address,
104 &account_id,
105 )?;
106 let Some(b) = base else {
107 return Err(AbstractError::Std(cosmwasm_std::StdError::generic_err(
108 format!(
109 "Version control does not have account id of account {account_addr}"
110 ),
111 )));
112 };
113 if b.addr() == account_addr {
114 Ok(GovernanceDetails::SubAccount {
115 account: account_addr,
116 })
117 } else {
118 Err(AbstractError::Std(cosmwasm_std::StdError::generic_err(
119 "Verification of sub-account failed, account has different account ids",
120 )))
121 }
122 }
123 GovernanceDetails::External {
124 governance_address,
125 governance_type,
126 } => {
127 let addr = deps.api.addr_validate(&governance_address)?;
128
129 if governance_type.len() < MIN_GOV_TYPE_LENGTH {
130 return Err(AbstractError::FormattingError {
131 object: "governance type".into(),
132 expected: "at least 3 characters".into(),
133 actual: governance_type.len().to_string(),
134 });
135 }
136 if governance_type.len() > MAX_GOV_TYPE_LENGTH {
137 return Err(AbstractError::FormattingError {
138 object: "governance type".into(),
139 expected: "at most 64 characters".into(),
140 actual: governance_type.len().to_string(),
141 });
142 }
143 if governance_type.contains(|c: char| !c.is_ascii_alphanumeric() && c != '-') {
144 return Err(AbstractError::FormattingError {
145 object: "governance type".into(),
146 expected: "alphanumeric characters and hyphens".into(),
147 actual: governance_type,
148 });
149 }
150
151 if governance_type != governance_type.to_lowercase() {
152 return Err(AbstractError::FormattingError {
153 object: "governance type".into(),
154 expected: governance_type.to_ascii_lowercase(),
155 actual: governance_type,
156 });
157 }
158
159 Ok(GovernanceDetails::External {
160 governance_address: addr,
161 governance_type,
162 })
163 }
164 GovernanceDetails::Renounced {} => Ok(GovernanceDetails::Renounced {}),
165 GovernanceDetails::NFT {
166 collection_addr,
167 token_id,
168 } => Ok(GovernanceDetails::NFT {
169 collection_addr: deps.api.addr_validate(&collection_addr.to_string())?,
170 token_id,
171 }),
172 GovernanceDetails::AbstractAccount { address } => {
173 Ok(GovernanceDetails::AbstractAccount { address })
174 }
175 }
176 }
177}
178
179impl GovernanceDetails<Addr> {
180 pub fn owner_address(&self, querier: &QuerierWrapper) -> Option<Addr> {
182 match self {
183 GovernanceDetails::Monarchy { monarch } => Some(monarch.clone()),
184 GovernanceDetails::SubAccount { account } => Some(account.clone()),
185 GovernanceDetails::External {
186 governance_address, ..
187 } => Some(governance_address.clone()),
188 GovernanceDetails::Renounced {} => None,
189 GovernanceDetails::NFT {
190 collection_addr,
191 token_id,
192 } => {
193 let res: Option<cw721::OwnerOfResponse> = querier
194 .query_wasm_smart(
195 collection_addr,
196 &cw721::Cw721QueryMsg::OwnerOf {
197 token_id: token_id.to_string(),
198 include_expired: None,
199 },
200 )
201 .ok();
202 res.map(|owner_response| Addr::unchecked(owner_response.owner))
203 }
204 GovernanceDetails::AbstractAccount { address } => Some(address.to_owned()),
205 }
206 }
207}
208
209impl From<GovernanceDetails<Addr>> for GovernanceDetails<String> {
210 fn from(value: GovernanceDetails<Addr>) -> Self {
211 match value {
212 GovernanceDetails::Monarchy { monarch } => GovernanceDetails::Monarchy {
213 monarch: monarch.into_string(),
214 },
215 GovernanceDetails::SubAccount { account } => GovernanceDetails::SubAccount {
216 account: account.into_string(),
217 },
218 GovernanceDetails::External {
219 governance_address,
220 governance_type,
221 } => GovernanceDetails::External {
222 governance_address: governance_address.into_string(),
223 governance_type,
224 },
225 GovernanceDetails::Renounced {} => GovernanceDetails::Renounced {},
226 GovernanceDetails::NFT {
227 collection_addr,
228 token_id,
229 } => GovernanceDetails::NFT {
230 collection_addr: collection_addr.to_string(),
231 token_id,
232 },
233 GovernanceDetails::AbstractAccount { address } => {
234 GovernanceDetails::AbstractAccount { address }
235 }
236 }
237 }
238}
239
240impl<T: AddressLike> std::fmt::Display for GovernanceDetails<T> {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 let str = match self {
243 GovernanceDetails::Monarchy { .. } => "monarch",
244 GovernanceDetails::SubAccount { .. } => "sub-account",
245 GovernanceDetails::External {
246 governance_type, ..
247 } => governance_type.as_str(),
248 GovernanceDetails::Renounced {} => "renounced",
249 GovernanceDetails::NFT { .. } => "nft",
250 GovernanceDetails::AbstractAccount { .. } => "abstract-account",
251 };
252 write!(f, "{str}")
253 }
254}
255
256#[cosmwasm_schema::cw_serde]
257pub struct TopLevelOwnerResponse {
258 pub address: Addr,
259}
260
261#[cfg(test)]
262mod test {
263 #![allow(clippy::needless_borrows_for_generic_args)]
264 use super::*;
265
266 use cosmwasm_std::testing::mock_dependencies;
267
268 #[coverage_helper::test]
269 fn test_verify() {
270 let deps = mock_dependencies();
271 let owner = deps.api.addr_make("monarch");
272 let gov = GovernanceDetails::Monarchy {
273 monarch: owner.to_string(),
274 };
275 assert!(gov.verify(deps.as_ref()).is_ok());
276
277 let gov_addr = deps.api.addr_make("gov_addr");
278 let gov = GovernanceDetails::External {
279 governance_address: gov_addr.to_string(),
280 governance_type: "external-multisig".to_string(),
281 };
282 assert!(gov.verify(deps.as_ref()).is_ok());
283
284 let gov = GovernanceDetails::Monarchy {
285 monarch: "NOT_OK".to_string(),
286 };
287 assert!(gov.verify(deps.as_ref()).is_err());
288 let gov = GovernanceDetails::External {
289 governance_address: "gov_address".to_string(),
290 governance_type: "gov_type".to_string(),
291 };
292 assert!(gov.verify(deps.as_ref()).is_err());
294
295 let gov_address = deps.api.addr_make("gov_address");
297 let gov = GovernanceDetails::External {
298 governance_address: gov_address.to_string(),
299 governance_type: "gov".to_string(),
300 };
301 assert!(gov.verify(deps.as_ref()).is_err());
302
303 let gov = GovernanceDetails::External {
305 governance_address: gov_address.to_string(),
306 governance_type: "a".repeat(190),
307 };
308 assert!(gov.verify(deps.as_ref()).is_err());
309
310 let gov = GovernanceDetails::External {
312 governance_address: "NOT_OK".to_string(),
313 governance_type: "gov_type".to_string(),
314 };
315 assert!(gov.verify(deps.as_ref()).is_err());
316
317 let collection_addr = deps.api.addr_make("collection_addr");
319 let gov = GovernanceDetails::NFT {
320 collection_addr: collection_addr.to_string(),
321 token_id: "1".to_string(),
322 };
323 assert!(gov.verify(deps.as_ref()).is_ok());
324 }
325}