use abstract_adapter::mock::{MockExecMsg, MockInitMsg};
use abstract_integration_tests::*;
use abstract_interface::*;
use abstract_manager::{contract::CONTRACT_VERSION, error::ManagerError};
use abstract_std::{
manager::{ManagerModuleInfo, ModuleInstallConfig, ModuleVersionsResponse},
objects::{
fee::FixedFee,
gov_type::{GovAction, GovernanceDetails},
module::{ModuleInfo, ModuleVersion, Monetization},
module_reference::ModuleReference,
namespace::Namespace,
ownership, AccountId, ABSTRACT_ACCOUNT_ID,
},
version_control::{NamespaceResponse, UpdateModule},
PROXY,
};
use abstract_testing::prelude::*;
use anyhow::Error;
use cosmwasm_std::{coin, CosmosMsg, Uint128};
use cw721::OwnerOfResponse;
use cw_orch::{
mock::{cw_multi_test::MockApiBech32, MockBase},
prelude::*,
};
use speculoos::prelude::*;
fn deploy_and_mint_nft(
chain: MockBase<MockApiBech32>,
sender: Addr,
) -> Result<(String, Addr), Error> {
let token_id = String::from("1");
let cw721_contract = Box::new(ContractWrapper::new(
cw721_base::entry::execute,
cw721_base::entry::instantiate,
cw721_base::entry::query,
));
let cw721_id = chain.app.borrow_mut().store_code(cw721_contract);
let res = chain.instantiate(
cw721_id,
&cw721_base::InstantiateMsg {
name: "testcollection".to_string(),
symbol: "TEST".to_string(),
minter: sender.to_string(),
},
Some("test-account-nft-collection"),
Some(&sender),
&[],
)?;
let nft_addr = res.instantiated_contract_address()?;
mint_nft(&chain, sender, token_id.clone(), &nft_addr)?;
Ok((token_id, nft_addr))
}
fn mint_nft(
chain: &MockBase<MockApiBech32>,
owner: impl Into<String>,
token_id: impl Into<String>,
nft_addr: &Addr,
) -> anyhow::Result<()> {
chain.execute(
&cw721_base::ExecuteMsg::<Option<Empty>, Empty>::Mint {
token_id: token_id.into(),
owner: owner.into(),
token_uri: None,
extension: None,
},
&[],
nft_addr,
)?;
Ok(())
}
#[test]
fn instantiate() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let account = create_default_account(&deployment.account_factory)?;
let modules = account.manager.module_infos(None, None)?.module_infos;
assert_that!(&modules).has_length(1);
assert_that(&modules[0]).is_equal_to(&ManagerModuleInfo {
address: account.proxy.address()?,
id: PROXY.to_string(),
version: cw2::ContractVersion {
contract: PROXY.into(),
version: CONTRACT_VERSION.into(),
},
});
assert_that!(account.manager.config()?).is_equal_to(abstract_std::manager::ConfigResponse {
version_control_address: deployment.version_control.address()?,
module_factory_address: deployment.module_factory.address()?,
account_id: TEST_ACCOUNT_ID,
is_suspended: false,
});
take_storage_snapshot!(chain, "instantiate_proxy");
Ok(())
}
#[test]
fn exec_through_manager() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let account = create_default_account(&deployment.account_factory)?;
chain.set_balance(&account.proxy.address()?, vec![Coin::new(100_000, TTOKEN)])?;
let proxy_balance = chain
.bank_querier()
.balance(account.proxy.address()?, None)?;
assert_that!(proxy_balance).is_equal_to(vec![Coin::new(100_000, TTOKEN)]);
let burn_amount = vec![Coin::new(10_000, TTOKEN)];
account.manager.exec_on_module(
cosmwasm_std::to_json_binary(&abstract_std::proxy::ExecuteMsg::ModuleAction {
msgs: vec![CosmosMsg::Bank(cosmwasm_std::BankMsg::Burn {
amount: burn_amount,
})],
})?,
PROXY.to_string(),
&[],
)?;
let proxy_balance = chain
.bank_querier()
.balance(account.proxy.address()?, None)?;
assert_that!(proxy_balance).is_equal_to(vec![Coin::new(100_000 - 10_000, TTOKEN)]);
take_storage_snapshot!(chain, "exec_through_manager");
Ok(())
}
#[test]
fn default_without_response_data() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let account = create_default_account(&deployment.account_factory)?;
let _staking_adapter_one = init_mock_adapter(chain.clone(), &deployment, None, account.id()?)?;
install_adapter(&account.manager, TEST_MODULE_ID)?;
chain.set_balance(&account.proxy.address()?, vec![Coin::new(100_000, TTOKEN)])?;
let resp = account.manager.execute_on_module(
TEST_MODULE_ID,
Into::<abstract_std::adapter::ExecuteMsg<MockExecMsg>>::into(MockExecMsg {}),
)?;
assert_that!(resp.data).is_none();
take_storage_snapshot!(chain, "default_without_response_data");
Ok(())
}
#[test]
fn with_response_data() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
Abstract::deploy_on(chain.clone(), sender.to_string())?;
abstract_integration_tests::manager::with_response_data(chain.clone())?;
take_storage_snapshot!(chain, "proxy_with_response_data");
Ok(())
}
#[test]
fn install_standalone_modules() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let account = AbstractAccount::new(&deployment, AccountId::local(0));
let standalone1_contract = Box::new(ContractWrapper::new(
mock_modules::standalone_cw2::mock_execute,
mock_modules::standalone_cw2::mock_instantiate,
mock_modules::standalone_cw2::mock_query,
));
let standalone1_id = chain.app.borrow_mut().store_code(standalone1_contract);
let standalone2_contract = Box::new(ContractWrapper::new(
mock_modules::standalone_no_cw2::mock_execute,
mock_modules::standalone_no_cw2::mock_instantiate,
mock_modules::standalone_no_cw2::mock_query,
));
let standalone2_id = chain.app.borrow_mut().store_code(standalone2_contract);
deployment.version_control.propose_modules(vec![(
ModuleInfo {
namespace: Namespace::new("abstract")?,
name: "standalone1".to_owned(),
version: ModuleVersion::Version(mock_modules::V1.to_owned()),
},
ModuleReference::Standalone(standalone1_id),
)])?;
account.install_module("abstract:standalone1", Some(&MockInitMsg {}), None)?;
deployment.version_control.propose_modules(vec![(
ModuleInfo {
namespace: Namespace::new("abstract")?,
name: "standalone2".to_owned(),
version: ModuleVersion::Version(mock_modules::V1.to_owned()),
},
ModuleReference::Standalone(standalone2_id),
)])?;
account.install_module("abstract:standalone2", Some(&MockInitMsg {}), None)?;
take_storage_snapshot!(chain, "proxy_install_standalone_modules");
Ok(())
}
#[test]
fn install_standalone_versions_not_met() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let account = AbstractAccount::new(&deployment, AccountId::local(0));
let standalone1_contract = Box::new(ContractWrapper::new(
mock_modules::standalone_cw2::mock_execute,
mock_modules::standalone_cw2::mock_instantiate,
mock_modules::standalone_cw2::mock_query,
));
let standalone1_id = chain.app.borrow_mut().store_code(standalone1_contract);
deployment.version_control.propose_modules(vec![(
ModuleInfo {
namespace: Namespace::new("abstract")?,
name: "standalone1".to_owned(),
version: ModuleVersion::Version(mock_modules::V2.to_owned()),
},
ModuleReference::Standalone(standalone1_id),
)])?;
let err = account
.install_module("abstract:standalone1", Some(&MockInitMsg {}), None)
.unwrap_err();
if let AbstractInterfaceError::Orch(err) = err {
let err: ManagerError = err.downcast()?;
assert_eq!(
err,
ManagerError::Abstract(abstract_std::AbstractError::UnequalModuleData {
cw2: mock_modules::V1.to_owned(),
module: mock_modules::V2.to_owned(),
})
);
} else {
panic!("wrong error type")
};
Ok(())
}
#[test]
fn install_multiple_modules() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
chain.add_balance(&sender, vec![coin(86, "token1"), coin(500, "token2")])?;
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let account = AbstractAccount::new(&deployment, ABSTRACT_ACCOUNT_ID);
let standalone1_contract = Box::new(ContractWrapper::new(
mock_modules::standalone_cw2::mock_execute,
mock_modules::standalone_cw2::mock_instantiate,
mock_modules::standalone_cw2::mock_query,
));
let standalone1_id = chain.app.borrow_mut().store_code(standalone1_contract);
let standalone2_contract = Box::new(ContractWrapper::new(
mock_modules::standalone_no_cw2::mock_execute,
mock_modules::standalone_no_cw2::mock_instantiate,
mock_modules::standalone_no_cw2::mock_query,
));
let standalone2_id = chain.app.borrow_mut().store_code(standalone2_contract);
deployment.version_control.propose_modules(vec![
(
ModuleInfo {
namespace: Namespace::new("abstract")?,
name: "standalone1".to_owned(),
version: ModuleVersion::Version(mock_modules::V1.to_owned()),
},
ModuleReference::Standalone(standalone1_id),
),
(
ModuleInfo {
namespace: Namespace::new("abstract")?,
name: "standalone2".to_owned(),
version: ModuleVersion::Version(mock_modules::V1.to_owned()),
},
ModuleReference::Standalone(standalone2_id),
),
])?;
let monetization = Monetization::InstallFee(FixedFee::new(&coin(42, "token1")));
deployment.version_control.update_module_configuration(
"standalone1".to_owned(),
Namespace::new("abstract").unwrap(),
UpdateModule::Versioned {
version: mock_modules::V1.to_owned(),
metadata: None,
monetization: Some(monetization),
instantiation_funds: None,
},
)?;
deployment.version_control.update_module_configuration(
"standalone2".to_owned(),
Namespace::new("abstract").unwrap(),
UpdateModule::Versioned {
version: mock_modules::V1.to_owned(),
metadata: None,
monetization: None,
instantiation_funds: Some(vec![coin(42, "token1"), coin(500, "token2")]),
},
)?;
let err = account
.install_modules(
vec![
ModuleInstallConfig::new(
ModuleInfo::from_id_latest("abstract:standalone1")?,
Some(to_json_binary(&MockInitMsg {}).unwrap()),
),
ModuleInstallConfig::new(
ModuleInfo::from_id_latest("abstract:standalone2")?,
Some(to_json_binary(&MockInitMsg {}).unwrap()),
),
],
Some(&[coin(86, "token1"), coin(500, "token2")]),
)
.unwrap_err();
assert!(err.root().to_string().contains(&format!(
"Expected {:?}, sent {:?}",
vec![coin(84, "token1"), coin(500, "token2")],
vec![coin(86, "token1"), coin(500, "token2")]
)));
account.install_modules_auto(vec![
ModuleInstallConfig::new(
ModuleInfo::from_id_latest("abstract:standalone1")?,
Some(to_json_binary(&MockInitMsg {}).unwrap()),
),
ModuleInstallConfig::new(
ModuleInfo::from_id_latest("abstract:standalone2")?,
Some(to_json_binary(&MockInitMsg {}).unwrap()),
),
])?;
let account_module_versions = account.manager.module_versions(vec![
String::from("abstract:standalone1"),
String::from("abstract:standalone2"),
])?;
assert_eq!(
account_module_versions,
ModuleVersionsResponse {
versions: vec![
cw2::ContractVersion {
contract: String::from("abstract:standalone1"),
version: String::from(mock_modules::V1),
},
cw2::ContractVersion {
contract: String::from("abstract:standalone2"),
version: String::from(mock_modules::V1),
},
]
}
);
let account_module_addresses = account.manager.module_addresses(vec![
String::from("abstract:standalone1"),
String::from("abstract:standalone2"),
])?;
let (standalone_addr1, standalone_addr2) = match &account_module_addresses.modules[..] {
[(_app1, addr1), (_app2, addr2)] => (addr1.clone(), addr2.clone()),
_ => panic!("bad result from module_addresses"),
};
let s1_balance = chain.query_all_balances(&standalone_addr1)?;
let s2_balance = chain.query_all_balances(&standalone_addr2)?;
assert!(s1_balance.is_empty());
assert_eq!(s2_balance, vec![coin(42, "token1"), coin(500, "token2")]);
take_storage_snapshot!(chain, "proxy_install_multiple_modules");
Ok(())
}
#[test]
fn renounce_cleans_namespace() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let account = deployment.account_factory.create_new_account(
AccountDetails {
name: "foo".to_string(),
description: None,
link: None,
namespace: Some("bar".to_owned()),
base_asset: None,
install_modules: vec![],
account_id: None,
},
GovernanceDetails::Monarchy {
monarch: sender.to_string(),
},
None,
)?;
let namespace_result = deployment
.version_control
.namespace(Namespace::unchecked("bar"));
assert!(namespace_result.is_ok());
account
.manager
.update_ownership(ownership::GovAction::RenounceOwnership)?;
let namespace_result = deployment
.version_control
.namespace(Namespace::unchecked("bar"))?;
assert_eq!(namespace_result, NamespaceResponse::Unclaimed {});
let ownership = account.manager.ownership()?;
assert_eq!(ownership.owner, GovernanceDetails::Renounced {});
Ok(())
}
#[test]
fn nft_owner_success() -> Result<(), Error> {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let (token_id, nft_addr) = deploy_and_mint_nft(chain.clone(), sender.clone())?;
let gov = GovernanceDetails::NFT {
collection_addr: nft_addr.to_string(),
token_id: token_id.clone(),
};
let account = deployment.account_factory.create_new_account(
AccountDetails {
name: "foo".to_string(),
description: None,
link: None,
namespace: None,
base_asset: None,
install_modules: vec![],
account_id: None,
},
gov,
None,
)?;
let start_amnt = 100_000;
let burn_amnt = 10_000u128;
let start_balance = vec![Coin::new(start_amnt, TTOKEN)];
let burn_amount: Vec<Coin> = vec![Coin::new(burn_amnt, TTOKEN)];
let first_burn = Uint128::from(start_amnt).checked_sub(burn_amnt.into())?;
chain.set_balance(&account.proxy.address()?, start_balance.clone())?;
let burn_msg = abstract_std::proxy::ExecuteMsg::ModuleAction {
msgs: vec![CosmosMsg::Bank(cosmwasm_std::BankMsg::Burn {
amount: burn_amount,
})],
};
account.manager.execute_on_module(PROXY, &burn_msg)?;
let balance = chain.query_balance(&account.proxy.address()?, TTOKEN)?;
assert_eq!(balance.clone(), first_burn.clone());
let not_nft_holder = chain.addr_make_with_balance("test", vec![])?;
let res = account
.manager
.call_as(¬_nft_holder)
.execute_on_module(PROXY, &burn_msg);
assert!(&res.is_err());
let new_nft_owner = not_nft_holder;
chain.execute(
&cw721::Cw721ExecuteMsg::TransferNft {
recipient: new_nft_owner.to_string(),
token_id: token_id.clone(),
},
&[],
&Addr::unchecked(nft_addr.clone()),
)?;
let resp: OwnerOfResponse = chain.wasm_querier().smart_query(
nft_addr,
&cw721::Cw721QueryMsg::OwnerOf {
token_id: token_id.clone(),
include_expired: None,
},
)?;
assert_eq!(resp.owner, new_nft_owner);
let res = account.manager.execute_on_module(PROXY, &burn_msg);
assert!(&res.is_err());
account
.manager
.call_as(&new_nft_owner)
.execute_on_module(PROXY, burn_msg)?;
let balance = chain.query_balance(&account.proxy.address()?, TTOKEN)?;
assert_eq!(balance, first_burn.checked_sub(burn_amnt.into())?);
Ok(())
}
#[test]
fn nft_owner_immutable() -> Result<(), Error> {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let (token_id, nft_addr) = deploy_and_mint_nft(chain.clone(), sender.clone())?;
let gov = GovernanceDetails::NFT {
collection_addr: nft_addr.to_string(),
token_id: token_id.clone(),
};
let account = deployment.account_factory.create_new_account(
AccountDetails {
name: "foo".to_string(),
description: None,
link: None,
namespace: None,
base_asset: None,
install_modules: vec![],
account_id: None,
},
gov,
None,
)?;
let not_nft_owner = chain.addr_make("not_nft_owner");
let err: ManagerError = account
.manager
.update_ownership(GovAction::TransferOwnership {
new_owner: GovernanceDetails::Monarchy {
monarch: not_nft_owner.to_string(),
},
expiry: None,
})
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::ChangeOfNftOwned)
);
let err: ManagerError = account
.manager
.update_ownership(ownership::GovAction::RenounceOwnership)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::ChangeOfNftOwned)
);
let sub_account = account.create_sub_account(
AccountDetails {
name: "sub-foo".to_string(),
description: None,
link: None,
namespace: None,
base_asset: None,
install_modules: vec![],
account_id: None,
},
None,
)?;
let err: ManagerError = sub_account
.manager
.update_ownership(GovAction::TransferOwnership {
new_owner: GovernanceDetails::Monarchy {
monarch: not_nft_owner.to_string(),
},
expiry: None,
})
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::ChangeOfNftOwned)
);
let err: ManagerError = sub_account
.manager
.update_ownership(ownership::GovAction::RenounceOwnership)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::ChangeOfNftOwned)
);
Ok(())
}
#[test]
fn nft_pending_owner() -> Result<(), Error> {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let (token_id, nft_addr) = deploy_and_mint_nft(chain.clone(), sender.clone())?;
let gov = GovernanceDetails::NFT {
collection_addr: nft_addr.to_string(),
token_id: token_id.clone(),
};
let account =
deployment
.account_factory
.create_default_account(GovernanceDetails::Monarchy {
monarch: chain.sender_addr().to_string(),
})?;
let err: ManagerError = account
.manager
.update_ownership(GovAction::TransferOwnership {
new_owner: GovernanceDetails::NFT {
collection_addr: nft_addr.to_string(),
token_id: "falsy_token_id".to_owned(),
},
expiry: None,
})
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::TransferToRenounced)
);
account
.manager
.update_ownership(GovAction::TransferOwnership {
new_owner: gov.clone(),
expiry: None,
})?;
chain.execute(
&cw721_base::ExecuteMsg::<Option<Empty>, Empty>::Burn { token_id },
&[],
&nft_addr,
)?;
let ownership = account.manager.ownership()?;
assert_eq!(ownership.pending_owner.unwrap(), gov);
let err: ManagerError = account
.manager
.update_ownership(GovAction::AcceptOwnership)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::TransferNotFound)
);
let new_token_id = "2".to_owned();
mint_nft(&chain, chain.sender_addr(), &new_token_id, &nft_addr)?;
account
.manager
.update_ownership(GovAction::TransferOwnership {
new_owner: (GovernanceDetails::NFT {
collection_addr: nft_addr.to_string(),
token_id: new_token_id.clone(),
}),
expiry: None,
})?;
let err: ManagerError = account
.manager
.call_as(&chain.addr_make("not_nft_owner"))
.update_ownership(GovAction::AcceptOwnership)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::NotPendingOwner)
);
account
.manager
.update_ownership(GovAction::AcceptOwnership)?;
chain.execute(
&cw721_base::ExecuteMsg::<Option<Empty>, Empty>::Burn {
token_id: new_token_id,
},
&[],
&nft_addr,
)?;
let err: ManagerError = account
.manager
.update_info(Some("RIP Account".to_owned()), None, None)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ManagerError::Ownership(ownership::GovOwnershipError::NoOwner)
);
Ok(())
}
#[test]
fn can_take_any_last_two_billion_accounts() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
deployment.account_factory.create_new_account(
AccountDetails {
name: "foo".to_string(),
description: None,
link: None,
namespace: Some("bar".to_owned()),
base_asset: None,
install_modules: vec![],
account_id: Some(2147483648),
},
GovernanceDetails::Monarchy {
monarch: sender.to_string(),
},
None,
)?;
let already_exists = deployment.account_factory.create_new_account(
AccountDetails {
name: "foo".to_string(),
description: None,
link: None,
namespace: Some("bar".to_owned()),
base_asset: None,
install_modules: vec![],
account_id: Some(2147483648),
},
GovernanceDetails::Monarchy {
monarch: sender.to_string(),
},
None,
);
assert!(already_exists.is_err());
Ok(())
}
#[test]
fn increment_not_effected_by_claiming() -> AResult {
let chain = MockBech32::new("mock");
let sender = chain.sender_addr();
let deployment = Abstract::deploy_on(chain.clone(), sender.to_string())?;
let next_account_id = deployment.account_factory.config()?.local_account_sequence;
assert_eq!(next_account_id, 1);
deployment.account_factory.create_new_account(
AccountDetails {
name: "foo".to_string(),
description: None,
link: None,
namespace: Some("bar".to_owned()),
base_asset: None,
install_modules: vec![],
account_id: Some(2147483648),
},
GovernanceDetails::Monarchy {
monarch: sender.to_string(),
},
None,
)?;
let next_account_id = deployment.account_factory.config()?.local_account_sequence;
assert_eq!(next_account_id, 1);
deployment
.account_factory
.create_default_account(GovernanceDetails::Monarchy {
monarch: sender.to_string(),
})?;
let next_account_id = deployment.account_factory.config()?.local_account_sequence;
assert_eq!(next_account_id, 2);
Ok(())
}