use crate::{account::state::ACCOUNT_ID, native_addrs, registry};
use cosmwasm_std::{Addr, Deps, QuerierWrapper};
use cw_address_like::AddressLike;
use cw_utils::Expiration;
use crate::AbstractError;
use super::ownership::cw721;
const MIN_GOV_TYPE_LENGTH: usize = 4;
const MAX_GOV_TYPE_LENGTH: usize = 64;
#[cosmwasm_schema::cw_serde]
#[derive(Eq)]
#[non_exhaustive]
pub enum GovernanceDetails<T: AddressLike> {
Monarchy {
monarch: T,
},
SubAccount {
account: T,
},
External {
governance_address: T,
governance_type: String,
},
NFT {
collection_addr: T,
token_id: String,
},
AbstractAccount {
address: Addr,
},
Renounced {},
}
#[cosmwasm_schema::cw_serde]
pub enum GovAction {
TransferOwnership {
new_owner: GovernanceDetails<String>,
expiry: Option<Expiration>,
},
AcceptOwnership,
RenounceOwnership,
}
impl GovernanceDetails<String> {
pub fn verify(self, deps: Deps) -> Result<GovernanceDetails<Addr>, AbstractError> {
match self {
GovernanceDetails::Monarchy { monarch } => {
let addr = deps.api.addr_validate(&monarch)?;
Ok(GovernanceDetails::Monarchy { monarch: addr })
}
GovernanceDetails::SubAccount { account } => {
let account_addr = deps.api.addr_validate(&account)?;
let abstract_code_id = native_addrs::abstract_code_id(&deps.querier, account)?;
let registry_address = native_addrs::registry_address(deps, abstract_code_id)?;
let registry_address = deps.api.addr_humanize(®istry_address)?;
let account_id = ACCOUNT_ID.query(&deps.querier, account_addr.clone())?;
let base = registry::state::ACCOUNT_ADDRESSES.query(
&deps.querier,
registry_address,
&account_id,
)?;
let Some(b) = base else {
return Err(AbstractError::Std(cosmwasm_std::StdError::generic_err(
format!(
"Version control does not have account id of account {account_addr}"
),
)));
};
if b.addr() == account_addr {
Ok(GovernanceDetails::SubAccount {
account: account_addr,
})
} else {
Err(AbstractError::Std(cosmwasm_std::StdError::generic_err(
"Verification of sub-account failed, account has different account ids",
)))
}
}
GovernanceDetails::External {
governance_address,
governance_type,
} => {
let addr = deps.api.addr_validate(&governance_address)?;
if governance_type.len() < MIN_GOV_TYPE_LENGTH {
return Err(AbstractError::FormattingError {
object: "governance type".into(),
expected: "at least 3 characters".into(),
actual: governance_type.len().to_string(),
});
}
if governance_type.len() > MAX_GOV_TYPE_LENGTH {
return Err(AbstractError::FormattingError {
object: "governance type".into(),
expected: "at most 64 characters".into(),
actual: governance_type.len().to_string(),
});
}
if governance_type.contains(|c: char| !c.is_ascii_alphanumeric() && c != '-') {
return Err(AbstractError::FormattingError {
object: "governance type".into(),
expected: "alphanumeric characters and hyphens".into(),
actual: governance_type,
});
}
if governance_type != governance_type.to_lowercase() {
return Err(AbstractError::FormattingError {
object: "governance type".into(),
expected: governance_type.to_ascii_lowercase(),
actual: governance_type,
});
}
Ok(GovernanceDetails::External {
governance_address: addr,
governance_type,
})
}
GovernanceDetails::Renounced {} => Ok(GovernanceDetails::Renounced {}),
GovernanceDetails::NFT {
collection_addr,
token_id,
} => Ok(GovernanceDetails::NFT {
collection_addr: deps.api.addr_validate(&collection_addr.to_string())?,
token_id,
}),
GovernanceDetails::AbstractAccount { address } => {
Ok(GovernanceDetails::AbstractAccount { address })
}
}
}
}
impl GovernanceDetails<Addr> {
pub fn owner_address(&self, querier: &QuerierWrapper) -> Option<Addr> {
match self {
GovernanceDetails::Monarchy { monarch } => Some(monarch.clone()),
GovernanceDetails::SubAccount { account } => Some(account.clone()),
GovernanceDetails::External {
governance_address, ..
} => Some(governance_address.clone()),
GovernanceDetails::Renounced {} => None,
GovernanceDetails::NFT {
collection_addr,
token_id,
} => {
let res: Option<cw721::OwnerOfResponse> = querier
.query_wasm_smart(
collection_addr,
&cw721::Cw721QueryMsg::OwnerOf {
token_id: token_id.to_string(),
include_expired: None,
},
)
.ok();
res.map(|owner_response| Addr::unchecked(owner_response.owner))
}
GovernanceDetails::AbstractAccount { address } => Some(address.to_owned()),
}
}
}
impl From<GovernanceDetails<Addr>> for GovernanceDetails<String> {
fn from(value: GovernanceDetails<Addr>) -> Self {
match value {
GovernanceDetails::Monarchy { monarch } => GovernanceDetails::Monarchy {
monarch: monarch.into_string(),
},
GovernanceDetails::SubAccount { account } => GovernanceDetails::SubAccount {
account: account.into_string(),
},
GovernanceDetails::External {
governance_address,
governance_type,
} => GovernanceDetails::External {
governance_address: governance_address.into_string(),
governance_type,
},
GovernanceDetails::Renounced {} => GovernanceDetails::Renounced {},
GovernanceDetails::NFT {
collection_addr,
token_id,
} => GovernanceDetails::NFT {
collection_addr: collection_addr.to_string(),
token_id,
},
GovernanceDetails::AbstractAccount { address } => {
GovernanceDetails::AbstractAccount { address }
}
}
}
}
impl<T: AddressLike> std::fmt::Display for GovernanceDetails<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
GovernanceDetails::Monarchy { .. } => "monarch",
GovernanceDetails::SubAccount { .. } => "sub-account",
GovernanceDetails::External {
governance_type, ..
} => governance_type.as_str(),
GovernanceDetails::Renounced {} => "renounced",
GovernanceDetails::NFT { .. } => "nft",
GovernanceDetails::AbstractAccount { .. } => "abstract-account",
};
write!(f, "{str}")
}
}
#[cosmwasm_schema::cw_serde]
pub struct TopLevelOwnerResponse {
pub address: Addr,
}
#[cfg(test)]
mod test {
#![allow(clippy::needless_borrows_for_generic_args)]
use super::*;
use cosmwasm_std::testing::mock_dependencies;
#[coverage_helper::test]
fn test_verify() {
let deps = mock_dependencies();
let owner = deps.api.addr_make("monarch");
let gov = GovernanceDetails::Monarchy {
monarch: owner.to_string(),
};
assert!(gov.verify(deps.as_ref()).is_ok());
let gov_addr = deps.api.addr_make("gov_addr");
let gov = GovernanceDetails::External {
governance_address: gov_addr.to_string(),
governance_type: "external-multisig".to_string(),
};
assert!(gov.verify(deps.as_ref()).is_ok());
let gov = GovernanceDetails::Monarchy {
monarch: "NOT_OK".to_string(),
};
assert!(gov.verify(deps.as_ref()).is_err());
let gov = GovernanceDetails::External {
governance_address: "gov_address".to_string(),
governance_type: "gov_type".to_string(),
};
assert!(gov.verify(deps.as_ref()).is_err());
let gov_address = deps.api.addr_make("gov_address");
let gov = GovernanceDetails::External {
governance_address: gov_address.to_string(),
governance_type: "gov".to_string(),
};
assert!(gov.verify(deps.as_ref()).is_err());
let gov = GovernanceDetails::External {
governance_address: gov_address.to_string(),
governance_type: "a".repeat(190),
};
assert!(gov.verify(deps.as_ref()).is_err());
let gov = GovernanceDetails::External {
governance_address: "NOT_OK".to_string(),
governance_type: "gov_type".to_string(),
};
assert!(gov.verify(deps.as_ref()).is_err());
let collection_addr = deps.api.addr_make("collection_addr");
let gov = GovernanceDetails::NFT {
collection_addr: collection_addr.to_string(),
token_id: "1".to_string(),
};
assert!(gov.verify(deps.as_ref()).is_ok());
}
}