use std::fmt::Display;
use cosmwasm_std::{ensure, Env, StdError, StdResult};
use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey};
use crate::{constants::CHAIN_DELIMITER, objects::chain_name::ChainName, AbstractError};
pub const MAX_TRACE_LENGTH: usize = 6;
pub(crate) const LOCAL: &str = "local";
#[cosmwasm_schema::cw_serde]
pub enum AccountTrace {
Local,
Remote(Vec<ChainName>),
}
impl KeyDeserialize for &AccountTrace {
type Output = AccountTrace;
#[inline(always)]
fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
Ok(AccountTrace::from_string(String::from_vec(value)?))
}
}
impl<'a> PrimaryKey<'a> for AccountTrace {
type Prefix = ();
type SubPrefix = ();
type Suffix = Self;
type SuperSuffix = Self;
fn key(&self) -> Vec<cw_storage_plus::Key> {
match self {
AccountTrace::Local => LOCAL.key(),
AccountTrace::Remote(chain_name) => {
let len = chain_name.len();
chain_name
.iter()
.enumerate()
.flat_map(|(s, c)| {
if s == len - 1 {
vec![c.str_ref().key()]
} else {
vec![c.str_ref().key(), CHAIN_DELIMITER.key()]
}
})
.flatten()
.collect::<Vec<Key>>()
}
}
}
}
impl KeyDeserialize for AccountTrace {
type Output = AccountTrace;
#[inline(always)]
fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
Ok(AccountTrace::from_string(String::from_vec(value)?))
}
}
impl<'a> Prefixer<'a> for AccountTrace {
fn prefix(&self) -> Vec<Key> {
self.key()
}
}
impl AccountTrace {
pub fn verify(&self) -> Result<(), AbstractError> {
match self {
AccountTrace::Local => Ok(()),
AccountTrace::Remote(chain_trace) => {
ensure!(
chain_trace.len() <= MAX_TRACE_LENGTH,
AbstractError::FormattingError {
object: "chain-seq".into(),
expected: format!("between 1 and {MAX_TRACE_LENGTH}"),
actual: chain_trace.len().to_string(),
}
);
for chain in chain_trace {
chain.verify()?;
if chain.as_str().eq(LOCAL) {
return Err(AbstractError::FormattingError {
object: "chain-seq".into(),
expected: "not 'local'".into(),
actual: chain.to_string(),
});
}
}
Ok(())
}
}
}
pub fn verify_remote(&self) -> Result<(), AbstractError> {
if &Self::Local == self {
Err(AbstractError::Std(StdError::generic_err(
"expected remote account trace",
)))
} else {
self.verify()
}
}
pub fn verify_local(&self) -> Result<(), AbstractError> {
if let &Self::Remote(..) = self {
return Err(AbstractError::Std(StdError::generic_err(
"expected local account trace",
)));
}
Ok(())
}
pub fn push_local_chain(&mut self, env: &Env) {
match &self {
AccountTrace::Local => {
*self = AccountTrace::Remote(vec![ChainName::new(env)]);
}
AccountTrace::Remote(path) => {
let mut path = path.clone();
path.push(ChainName::new(env));
*self = AccountTrace::Remote(path);
}
}
}
pub fn push_chain(&mut self, chain_name: ChainName) {
match &self {
AccountTrace::Local => {
*self = AccountTrace::Remote(vec![chain_name]);
}
AccountTrace::Remote(path) => {
let mut path = path.clone();
path.push(chain_name);
*self = AccountTrace::Remote(path);
}
}
}
pub(crate) fn from_string(trace: String) -> Self {
let acc = if trace == LOCAL {
Self::Local
} else {
Self::Remote(
trace
.split(CHAIN_DELIMITER)
.map(ChainName::_from_str)
.collect(),
)
};
acc
}
#[allow(unused)]
pub(crate) fn from_str(trace: &str) -> Result<Self, AbstractError> {
let acc = if trace == LOCAL {
Self::Local
} else {
Self::Remote(
trace
.split(CHAIN_DELIMITER)
.map(ChainName::_from_str)
.collect(),
)
};
acc.verify()?;
Ok(acc)
}
}
impl TryFrom<&str> for AccountTrace {
type Error = AbstractError;
fn try_from(trace: &str) -> Result<Self, Self::Error> {
if trace == LOCAL {
Ok(Self::Local)
} else {
let chain_trace: Vec<ChainName> = trace
.split(CHAIN_DELIMITER)
.map(|t| ChainName::from_string(t.to_string()))
.collect::<Result<Vec<_>, _>>()?;
Ok(Self::Remote(chain_trace))
}
}
}
impl Display for AccountTrace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AccountTrace::Local => write!(f, "{}", LOCAL),
AccountTrace::Remote(chain_name) => write!(
f,
"{}",
chain_name
.iter()
.map(|name| name.as_str())
.collect::<Vec<&str>>()
.join(CHAIN_DELIMITER)
),
}
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use cosmwasm_std::{testing::mock_dependencies, Addr, Order};
use cw_storage_plus::Map;
use super::*;
mod format {
use super::*;
use crate::objects::chain_name::MAX_CHAIN_NAME_LENGTH;
#[test]
fn local_works() {
let trace = AccountTrace::from_str(LOCAL).unwrap();
assert_eq!(trace, AccountTrace::Local);
}
#[test]
fn remote_works() {
let trace = AccountTrace::from_str("bitcoin").unwrap();
assert_eq!(
trace,
AccountTrace::Remote(vec![ChainName::from_str("bitcoin").unwrap()])
);
}
#[test]
fn remote_multi_works() {
let trace = AccountTrace::from_str("bitcoin>ethereum").unwrap();
assert_eq!(
trace,
AccountTrace::Remote(vec![
ChainName::from_str("bitcoin").unwrap(),
ChainName::from_str("ethereum").unwrap()
])
);
}
#[test]
fn remote_multi_multi_works() {
let trace = AccountTrace::from_str("bitcoin>ethereum>cosmos").unwrap();
assert_eq!(
trace,
AccountTrace::Remote(vec![
ChainName::from_str("bitcoin").unwrap(),
ChainName::from_str("ethereum").unwrap(),
ChainName::from_str("cosmos").unwrap(),
])
);
}
#[test]
fn local_empty_fails() {
AccountTrace::from_str("").unwrap_err();
}
#[test]
fn local_too_short_fails() {
AccountTrace::from_str("a").unwrap_err();
}
#[test]
fn local_too_long_fails() {
AccountTrace::from_str(&"a".repeat(MAX_CHAIN_NAME_LENGTH + 1)).unwrap_err();
}
#[test]
fn local_uppercase_fails() {
AccountTrace::from_str("AAAAA").unwrap_err();
}
#[test]
fn local_non_alphanumeric_fails() {
AccountTrace::from_str("a!aoeuoau").unwrap_err();
}
}
mod key {
use super::*;
fn mock_key() -> AccountTrace {
AccountTrace::Remote(vec![ChainName::from_str("bitcoin").unwrap()])
}
#[test]
fn storage_key_works() {
let mut deps = mock_dependencies();
let key = mock_key();
let map: Map<&AccountTrace, u64> = Map::new("map");
map.save(deps.as_mut().storage, &key, &42069).unwrap();
assert_eq!(map.load(deps.as_ref().storage, &key).unwrap(), 42069);
let items = map
.range(deps.as_ref().storage, None, None, Order::Ascending)
.map(|item| item.unwrap())
.collect::<Vec<_>>();
assert_eq!(items.len(), 1);
assert_eq!(items[0], (key, 42069));
}
#[test]
fn composite_key_works() {
let mut deps = mock_dependencies();
let key = mock_key();
let map: Map<(&AccountTrace, Addr), u64> = Map::new("map");
map.save(
deps.as_mut().storage,
(&key, Addr::unchecked("larry")),
&42069,
)
.unwrap();
map.save(
deps.as_mut().storage,
(&key, Addr::unchecked("jake")),
&69420,
)
.unwrap();
let items = map
.prefix(&key)
.range(deps.as_ref().storage, None, None, Order::Ascending)
.map(|item| item.unwrap())
.collect::<Vec<_>>();
assert_eq!(items.len(), 2);
assert_eq!(items[0], (Addr::unchecked("jake"), 69420));
assert_eq!(items[1], (Addr::unchecked("larry"), 42069));
}
}
}