use super::*;
use crate::{
error::Cw721ContractError,
extension::Cw721OnchainExtensions,
msg::{
AdditionalMintersResponse, CollectionExtensionMsg, CollectionInfoMsg, Cw721ExecuteMsg,
Cw721InstantiateMsg, Cw721QueryMsg, NftExtensionMsg, RoyaltyInfoResponse,
},
state::{
NftExtension, Trait, CREATOR, MAX_COLLECTION_DESCRIPTION_LENGTH,
MAX_ROYALTY_SHARE_DELTA_PCT, MAX_ROYALTY_SHARE_PCT, MINTER,
},
traits::{Cw721Execute, Cw721Query},
CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension,
RoyaltyInfo,
};
use cosmwasm_std::{
from_json,
testing::{mock_dependencies, mock_env},
Decimal, Empty, Timestamp,
};
use cw2::ContractVersion;
use cw_ownable::{Action, OwnershipError};
use unit_tests::contract_tests::MockAddrFactory;
use unit_tests::multi_tests::{CREATOR_ADDR, MINTER_ADDR, OTHER_ADDR};
#[test]
fn test_instantiation() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let err = Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&addrs.info("mr-t"),
Cw721InstantiateMsg {
name: "".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: None,
minter: None,
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::CollectionNameEmpty {});
let err = Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&addrs.info("mr-t"),
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "".into(),
collection_info_extension: None,
creator: None,
minter: None,
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::CollectionSymbolEmpty {});
Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&addrs.info("larry"),
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
minter: Some(addrs.addr("minter").into()),
creator: Some(addrs.addr("creator").into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let minter = MINTER
.get_ownership(deps.as_ref().storage)
.unwrap()
.owner
.map(|a| a.into_string());
assert_eq!(minter, Some(addrs.addr("minter").to_string()));
let creator = CREATOR
.get_ownership(deps.as_ref().storage)
.unwrap()
.owner
.map(|a| a.into_string());
assert_eq!(creator, Some(addrs.addr("creator").to_string()));
let version = cw2::get_contract_version(deps.as_ref().storage).unwrap();
assert_eq!(
version,
ContractVersion {
contract: "contract_name".into(),
version: "contract_version".into(),
},
);
}
#[test]
fn test_instantiation_with_proper_minter_and_creator() {
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let info_minter_and_creator = addrs.info("minter_and_creator");
Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info_minter_and_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: None,
minter: None,
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let minter = MINTER.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(minter, Some(info_minter_and_creator.sender.clone()));
let creator = CREATOR.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(creator, Some(info_minter_and_creator.sender));
}
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let info = addrs.info(OTHER_ADDR);
Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let minter = MINTER.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(minter, Some(addrs.addr(MINTER_ADDR)));
let creator = CREATOR.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(creator, Some(addrs.addr(CREATOR_ADDR)));
}
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let info = addrs.info(MINTER_ADDR);
Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: None,
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let minter = MINTER.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(minter, Some(info.sender));
let creator = CREATOR.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(creator, Some(addrs.addr(CREATOR_ADDR)));
}
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let info = addrs.info(CREATOR_ADDR);
Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: None,
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let minter = MINTER.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(minter, Some(addrs.addr(MINTER_ADDR)));
let creator = CREATOR.item.load(deps.as_ref().storage).unwrap().owner;
assert_eq!(creator, Some(info.sender));
}
}
#[test]
fn test_instantiation_with_collection_info() {
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let info_creator = addrs.info(CREATOR_ADDR);
let extension = Some(CollectionExtension {
description: "description".into(),
image: "https://moonphases.org".to_string(),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfo {
payment_address: addrs.addr("payment_address"),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let extension_msg = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: extension_msg,
creator: Some(addrs.addr(CREATOR_ADDR).to_string()),
minter: Some(addrs.addr(MINTER_ADDR).to_string()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let collection_info = Cw721OnchainExtensions::default()
.query_collection_info_and_extension(deps.as_ref())
.unwrap();
assert_eq!(collection_info.name, "collection_name");
assert_eq!(collection_info.symbol, "collection_symbol");
assert_eq!(collection_info.extension, extension);
}
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let info_creator = addrs.info(CREATOR_ADDR);
let extension_msg = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("invalid_url".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let err = Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: extension_msg,
creator: Some(addrs.addr(CREATOR_ADDR).to_string()),
minter: Some(addrs.addr(MINTER_ADDR).to_string()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::ParseError(url::ParseError::RelativeUrlWithoutBase)
);
let extension_msg = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("invalid_url".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let err = Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: extension_msg,
creator: Some(addrs.addr(CREATOR_ADDR).to_string()),
minter: Some(addrs.addr(MINTER_ADDR).to_string()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::ParseError(url::ParseError::RelativeUrlWithoutBase)
);
let extension_msg = Some(CollectionExtensionMsg {
description: Some("".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let err = Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: extension_msg,
creator: Some(addrs.addr(CREATOR_ADDR).to_string()),
minter: Some(addrs.addr(MINTER_ADDR).to_string()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::CollectionDescriptionEmpty {});
let extension_msg = Some(CollectionExtensionMsg {
description: Some("a".repeat(1001)),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let err = Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: extension_msg,
creator: Some(addrs.addr(CREATOR_ADDR).to_string()),
minter: Some(addrs.addr(MINTER_ADDR).to_string()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::CollectionDescriptionTooLong {
max_length: MAX_COLLECTION_DESCRIPTION_LENGTH
}
);
let extension_msg = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: (MAX_ROYALTY_SHARE_PCT * 2).to_string().parse().unwrap(),
}),
});
let err = Cw721OnchainExtensions::default()
.instantiate_with_version(
deps.as_mut(),
&mock_env(),
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: extension_msg,
creator: Some(addrs.addr(CREATOR_ADDR).to_string()),
minter: Some(addrs.addr(MINTER_ADDR).to_string()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::InvalidRoyalties(format!(
"Share cannot be greater than {MAX_ROYALTY_SHARE_PCT}%"
))
);
}
}
#[test]
fn test_collection_info_update() {
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let info_creator = addrs.info(CREATOR_ADDR);
let expected_instantiated_extension = Some(CollectionExtension {
description: "description".into(),
image: "https://moonphases.org".to_string(),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfo {
payment_address: addrs.addr("payment_address"),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let instantiated_extension_msg = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let contract = Cw721OnchainExtensions::default();
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: instantiated_extension_msg,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let empty_extension_msg = CollectionExtensionMsg {
description: None,
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: None,
royalty_info: None,
};
let empty_collection_info_msg = CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(empty_extension_msg),
};
contract
.execute(
deps.as_mut(),
&env,
&info_creator,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: empty_collection_info_msg,
},
)
.unwrap();
let collection_info = contract
.query_collection_info_and_extension(deps.as_ref())
.unwrap();
assert_eq!(collection_info.name, "collection_name");
assert_eq!(collection_info.symbol, "collection_symbol");
assert_eq!(collection_info.extension, expected_instantiated_extension);
let updated_extension_msg = CollectionExtensionMsg {
description: Some("new_description".into()),
image: Some("https://en.wikipedia.org/wiki/Non-fungible_token".to_string()),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: None, royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
contract
.execute(
deps.as_mut(),
&env,
&info_creator,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap();
let collection_info = Cw721OnchainExtensions::default()
.query_collection_info_and_extension(deps.as_ref())
.unwrap();
assert_eq!(collection_info.name, "new_collection_name");
assert_eq!(collection_info.symbol, "new_collection_symbol");
assert_eq!(
collection_info.extension,
Some(CollectionExtension {
description: "new_description".into(),
image: "https://en.wikipedia.org/wiki/Non-fungible_token".to_string(),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfo {
payment_address: addrs.addr("payment_address"),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
})
);
let updated_extension_msg = CollectionExtensionMsg {
description: None,
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(1)),
royalty_info: None,
};
let updated_collection_info_msg = CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(updated_extension_msg),
};
let info_minter = addrs.info(MINTER_ADDR);
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap();
let collection_info = Cw721OnchainExtensions::default()
.query_collection_info_and_extension(deps.as_ref())
.unwrap();
assert_eq!(collection_info.name, "new_collection_name");
assert_eq!(collection_info.symbol, "new_collection_symbol");
assert_eq!(
collection_info.extension,
Some(CollectionExtension {
description: "new_description".into(),
image: "https://en.wikipedia.org/wiki/Non-fungible_token".to_string(),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(1)),
royalty_info: Some(RoyaltyInfo {
payment_address: addrs.addr("payment_address"),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
})
);
}
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let info = addrs.info(CREATOR_ADDR);
let instantiated_extension_msg = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let contract = Cw721OnchainExtensions::default();
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: instantiated_extension_msg,
creator: None,
minter: None,
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let updated_extension_msg = CollectionExtensionMsg {
description: Some("".into()),
image: Some("https://en.wikipedia.org/wiki/Non-fungible_token".to_string()),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
let err = contract
.execute(
deps.as_mut(),
&env,
&info,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::CollectionDescriptionEmpty {});
let updated_extension_msg = CollectionExtensionMsg {
description: Some("a".repeat(1001)),
image: Some("https://en.wikipedia.org/wiki/Non-fungible_token".to_string()),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
let err = contract
.execute(
deps.as_mut(),
&env,
&info,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::CollectionDescriptionTooLong {
max_length: MAX_COLLECTION_DESCRIPTION_LENGTH
}
);
let updated_extension_msg = CollectionExtensionMsg {
description: Some("new_description".into()),
image: Some("invalid_url".to_string()),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
let err = contract
.execute(
deps.as_mut(),
&env,
&info,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::ParseError(url::ParseError::RelativeUrlWithoutBase)
);
let updated_extension_msg = CollectionExtensionMsg {
description: Some("new_description".into()),
image: Some("https://en.wikipedia.org/wiki/Non-fungible_token".to_string()),
explicit_content: Some(true),
external_link: Some("invalid_url".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
let err = contract
.execute(
deps.as_mut(),
&env,
&info,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::ParseError(url::ParseError::RelativeUrlWithoutBase)
);
let updated_extension_msg = CollectionExtensionMsg {
description: Some("new_description".into()),
image: Some("https://en.wikipedia.org/wiki/Non-fungible_token".to_string()),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT + MAX_ROYALTY_SHARE_DELTA_PCT - 1)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
let err = contract
.execute(
deps.as_mut(),
&env,
&info,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::InvalidRoyalties(format!(
"Share cannot be greater than {MAX_ROYALTY_SHARE_PCT}%"
))
);
let updated_extension_msg = CollectionExtensionMsg {
description: Some("new_description".into()),
image: Some("https://en.wikipedia.org/wiki/Non-fungible_token".to_string()),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT + MAX_ROYALTY_SHARE_DELTA_PCT + 1)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
let err = contract
.execute(
deps.as_mut(),
&env,
&info,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::InvalidRoyalties(format!(
"Share increase cannot be greater than {MAX_ROYALTY_SHARE_DELTA_PCT}%"
))
);
}
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let info = addrs.info(CREATOR_ADDR);
let instantiated_extension = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let contract = Cw721OnchainExtensions::default();
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: instantiated_extension,
creator: None,
minter: None,
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let updated_extension_msg = CollectionExtensionMsg {
description: Some("new_description".into()),
image: Some("https://en.wikipedia.org/wiki/Non-fungible_token".to_string()),
explicit_content: Some(true),
external_link: Some("https://github.com/CosmWasm/cw-nfts".to_string()),
banner_url: None,
start_trading_time: None,
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
};
let updated_collection_info_msg = CollectionInfoMsg {
name: Some("new_collection_name".into()),
symbol: Some("new_collection_symbol".into()),
extension: Some(updated_extension_msg),
};
let info_other = addrs.info(OTHER_ADDR);
let err = contract
.execute(
deps.as_mut(),
&env,
&info_other,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg.clone(),
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotCreator {});
contract
.execute(
deps.as_mut(),
&env,
&info,
Cw721ExecuteMsg::UpdateCreatorOwnership(Action::TransferOwnership {
new_owner: info_other.sender.to_string(),
expiry: None,
}),
)
.unwrap();
let err = contract
.execute(
deps.as_mut(),
&env,
&info_other,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg.clone(),
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotCreator {});
contract
.execute(
deps.as_mut(),
&env,
&info_other,
Cw721ExecuteMsg::UpdateCreatorOwnership(Action::AcceptOwnership {}),
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_other,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap();
}
{
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let info_creator = addrs.info(CREATOR_ADDR);
let info_minter = addrs.info(MINTER_ADDR);
let instantiated_extension = Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://moonphases.org".to_string()),
explicit_content: Some(true),
external_link: Some("https://moonphases.org".to_string()),
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(0)),
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr("payment_address").to_string(),
share: Decimal::percent(MAX_ROYALTY_SHARE_PCT)
.to_string()
.parse()
.unwrap(),
}),
});
let contract = Cw721OnchainExtensions::default();
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: instantiated_extension,
creator: None, minter: info_minter.sender.to_string().into(),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let updated_extension_msg = CollectionExtensionMsg {
description: None,
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(1)),
royalty_info: None,
};
let updated_collection_info_msg = CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(updated_extension_msg),
};
let err = contract
.execute(
deps.as_mut(),
&env,
&info_creator,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg.clone(),
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotMinter {});
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: updated_collection_info_msg,
},
)
.unwrap();
let collection_info = contract
.query_collection_info_and_extension(deps.as_ref())
.unwrap();
assert_eq!(
collection_info.extension.unwrap().start_trading_time,
Some(Timestamp::from_seconds(1))
);
}
}
#[test]
fn test_update_collection_info_non_creator_rejected() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let info_creator = addrs.info(CREATOR_ADDR);
let contract = Cw721OnchainExtensions::default();
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_creator,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: Some(CollectionExtensionMsg {
description: Some("description".into()),
image: Some("https://example.com/image.png".to_string()),
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: None,
royalty_info: None,
}),
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let info_other = addrs.info(OTHER_ADDR);
let err = contract
.execute(
deps.as_mut(),
&env,
&info_other,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: CollectionInfoMsg {
name: Some("new_name".into()),
symbol: Some("new_symbol".into()),
extension: None::<CollectionExtensionMsg<RoyaltyInfoResponse>>,
},
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotCreator {});
let err = contract
.execute(
deps.as_mut(),
&env,
&info_other,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(CollectionExtensionMsg {
description: Some("hacked".into()),
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: None,
royalty_info: None,
}),
},
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotCreator {});
let err = contract
.execute(
deps.as_mut(),
&env,
&info_other,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(CollectionExtensionMsg {
description: None,
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: None,
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr(OTHER_ADDR).to_string(),
share: Decimal::percent(5).to_string().parse().unwrap(),
}),
}),
},
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotCreator {});
let info_minter = addrs.info(MINTER_ADDR);
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(CollectionExtensionMsg {
description: None,
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: Some(Timestamp::from_seconds(999)),
royalty_info: None,
}),
},
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_creator,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(CollectionExtensionMsg {
description: Some("updated by creator".into()),
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: None,
royalty_info: None,
}),
},
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_creator,
Cw721ExecuteMsg::UpdateCollectionInfo {
collection_info: CollectionInfoMsg {
name: None,
symbol: None,
extension: Some(CollectionExtensionMsg {
description: None,
image: None,
explicit_content: None,
external_link: None,
banner_url: None,
start_trading_time: None,
royalty_info: Some(RoyaltyInfoResponse {
payment_address: addrs.addr(CREATOR_ADDR).to_string(),
share: Decimal::percent(5).to_string().parse().unwrap(),
}),
}),
},
},
)
.unwrap();
let collection_info = Cw721OnchainExtensions::default()
.query_collection_info_and_extension(deps.as_ref())
.unwrap();
let extension = collection_info.extension.unwrap();
let royalty = extension.royalty_info.unwrap();
assert_eq!(royalty.payment_address, addrs.addr(CREATOR_ADDR));
assert_eq!(royalty.share, Decimal::percent(5));
}
#[test]
fn test_nft_mint() {
{
let mut deps = mock_dependencies();
let contract = Cw721OnchainExtensions::default();
let mut addrs = MockAddrFactory::new(deps.api);
let info = addrs.info(CREATOR_ADDR);
let init_msg = Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
minter: None,
creator: None,
withdraw_address: None,
};
let env = mock_env();
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info,
init_msg,
"contract_name",
"contract_version",
)
.unwrap();
let token_id = "Enterprise";
let token_uri = Some("https://starships.example.com/Starship/Enterprise.json".into());
let exec_msg = Cw721ExecuteMsg::Mint {
token_id: token_id.to_string(),
owner: addrs.addr("john").to_string(),
token_uri: token_uri.clone(),
extension: None,
};
contract
.execute(deps.as_mut(), &env, &info, exec_msg)
.unwrap();
let res = contract
.query_nft_info(deps.as_ref().storage, token_id.into())
.unwrap();
assert_eq!(res.token_uri, token_uri);
assert_eq!(res.extension, None);
let exec_msg = Cw721ExecuteMsg::Mint {
token_id: token_id.to_string(), owner: addrs.addr("john").to_string(),
token_uri: "".to_string().into(), extension: None,
};
let err = contract
.execute(deps.as_mut(), &env, &info, exec_msg)
.unwrap_err();
assert_eq!(err, Cw721ContractError::Claimed {});
let info = addrs.info("john");
let exec_msg = Cw721ExecuteMsg::Mint {
token_id: "Enterprise".to_string(),
owner: addrs.addr("john").to_string(),
token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()),
extension: None,
};
let err = contract
.execute(deps.as_mut(), &env, &info, exec_msg)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotMinter {});
}
{
let mut deps = mock_dependencies();
let contract = Cw721OnchainExtensions::default();
let mut addrs = MockAddrFactory::new(deps.api);
let info = addrs.info(CREATOR_ADDR);
let init_msg = Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
minter: None,
creator: None,
withdraw_address: None,
};
let env = mock_env();
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info,
init_msg,
"contract_name",
"contract_version",
)
.unwrap();
let nft_1 = "1";
let uri_1 = Some("https://starships.example.com/Starship/Enterprise.json".into());
let extension_1_msg = Some(NftExtensionMsg {
description: Some("description1".into()),
name: Some("name1".to_string()),
attributes: Some(vec![Trait {
display_type: None,
trait_type: "type1".to_string(),
value: "value1".to_string(),
}]),
..NftExtensionMsg::default()
});
let exec_msg = Cw721ExecuteMsg::Mint {
token_id: nft_1.to_string(),
owner: addrs.addr("john").to_string(),
token_uri: uri_1.clone(),
extension: extension_1_msg.clone(),
};
contract
.execute(deps.as_mut(), &env, &info, exec_msg)
.unwrap();
let nft_info_1 = contract
.query_nft_info(deps.as_ref().storage, nft_1.into())
.unwrap();
assert_eq!(nft_info_1.token_uri, uri_1);
assert_eq!(
nft_info_1.extension,
Some(NftExtension {
description: Some("description1".into()),
name: Some("name1".to_string()),
attributes: Some(vec![Trait {
display_type: None,
trait_type: "type1".to_string(),
value: "value1".to_string(),
}]),
..NftExtension::default()
})
);
let nft_2 = "2";
let uri_2 = Some("ipfs://foo.bar".into());
let extension_2_msg = Some(NftExtensionMsg {
description: Some("other_description".into()),
name: Some("name1".to_string()),
attributes: Some(vec![Trait {
display_type: None,
trait_type: "type1".to_string(),
value: "value1".to_string(),
}]),
..NftExtensionMsg::default()
});
let exec_msg = Cw721ExecuteMsg::Mint {
token_id: nft_2.to_string(),
owner: addrs.addr("allen").to_string(),
token_uri: uri_2.clone(),
extension: extension_2_msg.clone(),
};
contract
.execute(deps.as_mut(), &env, &info, exec_msg)
.unwrap();
let nft_info_2 = contract
.query_nft_info(deps.as_ref().storage, nft_2.into())
.unwrap();
assert_eq!(nft_info_2.token_uri, uri_2);
assert_eq!(
nft_info_2.extension,
Some(NftExtension {
description: Some("other_description".into()),
name: Some("name1".to_string()),
attributes: Some(vec![Trait {
display_type: None,
trait_type: "type1".to_string(),
value: "value1".to_string(),
}]),
..NftExtension::default()
})
);
let res = contract
.query_nft_by_extension(
deps.as_ref().storage,
Some(NftExtension {
description: Some("other_description".into()), ..NftExtension::default()
}),
None,
None,
)
.unwrap();
assert!(res.is_some());
let result = res.unwrap();
assert_eq!(result.len(), 1);
let nft = result.first().unwrap();
assert_eq!(nft.token_uri, "ipfs://foo.bar".to_string().into());
let res = contract
.query_nft_by_extension(
deps.as_ref().storage,
Some(NftExtension {
name: Some("name1".into()), attributes: Some(vec![Trait {
display_type: None,
trait_type: "type1".to_string(),
value: "value1".to_string(),
}]),
..NftExtension::default()
}),
None,
None,
)
.unwrap();
assert!(res.is_some());
let result = res.unwrap();
assert_eq!(result.len(), 2);
}
}
#[test]
fn test_additional_minters_add_remove() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let contract = Cw721OnchainExtensions::default();
let info_minter = addrs.info(MINTER_ADDR);
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_minter,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let additional_minter = addrs.addr("additional_minter");
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter.to_string(),
},
)
.unwrap();
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: None,
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 1);
assert_eq!(response.minters[0], additional_minter.to_string());
let err = contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter.to_string(),
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::MinterAlreadyExists {});
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::RemoveMinter {
minter: additional_minter.to_string(),
},
)
.unwrap();
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: None,
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 0);
let err = contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::RemoveMinter {
minter: additional_minter.to_string(),
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::MinterNotFound {});
}
#[test]
fn test_additional_minter_can_mint() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let contract = Cw721OnchainExtensions::default();
let info_minter = addrs.info(MINTER_ADDR);
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_minter,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let additional_minter = addrs.addr("additional_minter");
let info_additional = addrs.info("additional_minter");
let err = contract
.execute(
deps.as_mut(),
&env,
&info_additional,
Cw721ExecuteMsg::Mint {
token_id: "1".to_string(),
owner: addrs.addr("owner1").to_string(),
token_uri: Some("https://example.com/1".into()),
extension: None,
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotMinter {});
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter.to_string(),
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_additional,
Cw721ExecuteMsg::Mint {
token_id: "1".to_string(),
owner: addrs.addr("owner1").to_string(),
token_uri: Some("https://example.com/1".into()),
extension: None,
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::Mint {
token_id: "2".to_string(),
owner: addrs.addr("owner2").to_string(),
token_uri: Some("https://example.com/2".into()),
extension: None,
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::RemoveMinter {
minter: additional_minter.to_string(),
},
)
.unwrap();
let err = contract
.execute(
deps.as_mut(),
&env,
&info_additional,
Cw721ExecuteMsg::Mint {
token_id: "3".to_string(),
owner: addrs.addr("owner3").to_string(),
token_uri: Some("https://example.com/3".into()),
extension: None,
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotMinter {});
}
#[test]
fn test_additional_minter_cannot_manage_minters() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let contract = Cw721OnchainExtensions::default();
let info_minter = addrs.info(MINTER_ADDR);
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_minter,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let additional_minter = addrs.addr("additional_minter");
let info_additional = addrs.info("additional_minter");
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter.to_string(),
},
)
.unwrap();
let err = contract
.execute(
deps.as_mut(),
&env,
&info_additional,
Cw721ExecuteMsg::AddMinter {
minter: addrs.addr("another").to_string(),
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotMinterOwner {});
let err = contract
.execute(
deps.as_mut(),
&env,
&info_additional,
Cw721ExecuteMsg::RemoveMinter {
minter: additional_minter.to_string(),
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotMinterOwner {});
let err = contract
.execute(
deps.as_mut(),
&env,
&info_additional,
Cw721ExecuteMsg::UpdateMinterOwnership(Action::TransferOwnership {
new_owner: addrs.addr("another").to_string(),
expiry: None,
}),
)
.unwrap_err();
assert!(matches!(err, Cw721ContractError::Ownership(..)));
let info_random = addrs.info("random");
let err = contract
.execute(
deps.as_mut(),
&env,
&info_random,
Cw721ExecuteMsg::Mint {
token_id: "1".to_string(),
owner: addrs.addr("owner").to_string(),
token_uri: Some("https://example.com/1".into()),
extension: None,
},
)
.unwrap_err();
assert_eq!(err, Cw721ContractError::NotMinter {});
}
#[test]
fn test_additional_minters_query_pagination() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let contract = Cw721OnchainExtensions::default();
let info_minter = addrs.info(MINTER_ADDR);
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_minter,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let minter_a = addrs.addr("minter_a");
let minter_b = addrs.addr("minter_b");
let minter_c = addrs.addr("minter_c");
for m in [&minter_a, &minter_b, &minter_c] {
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: m.to_string(),
},
)
.unwrap();
}
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: None,
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 3);
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: Some(1),
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 1);
let first = response.minters[0].clone();
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: Some(first),
limit: Some(10),
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 2);
}
#[test]
fn test_additional_minters_max_cap() {
use crate::state::MAX_ADDITIONAL_MINTERS;
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let contract = Cw721OnchainExtensions::default();
let info_minter = addrs.info(MINTER_ADDR);
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_minter,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let names = ["m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9"];
assert_eq!(names.len(), MAX_ADDITIONAL_MINTERS as usize);
for name in &names {
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: addrs.addr(name).to_string(),
},
)
.unwrap();
}
let err = contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: addrs.addr("one_too_many").to_string(),
},
)
.unwrap_err();
assert_eq!(
err,
Cw721ContractError::MaxAdditionalMintersExceeded {
max: MAX_ADDITIONAL_MINTERS
}
);
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::RemoveMinter {
minter: addrs.addr("m0").to_string(),
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: addrs.addr("replacement").to_string(),
},
)
.unwrap();
}
#[test]
fn test_additional_minters_cleared_on_ownership_transfer() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let env = mock_env();
let contract = Cw721OnchainExtensions::default();
let info_minter = addrs.info(MINTER_ADDR);
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_minter,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let additional_minter1 = addrs.addr("additional_minter1");
let additional_minter2 = addrs.addr("additional_minter2");
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter1.to_string(),
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter2.to_string(),
},
)
.unwrap();
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: None,
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 2);
let new_owner = addrs.addr("new_owner");
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::UpdateMinterOwnership(Action::TransferOwnership {
new_owner: new_owner.to_string(),
expiry: None,
}),
)
.unwrap();
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: None,
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 2);
let info_new_owner = addrs.info("new_owner");
contract
.execute(
deps.as_mut(),
&env,
&info_new_owner,
Cw721ExecuteMsg::UpdateMinterOwnership(Action::AcceptOwnership),
)
.unwrap();
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: None,
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 0);
}
#[test]
fn test_additional_minters_not_cleared_on_expired_transfer() {
let mut deps = mock_dependencies();
let mut addrs = MockAddrFactory::new(deps.api);
let mut env = mock_env();
let contract = Cw721OnchainExtensions::default();
let info_minter = addrs.info(MINTER_ADDR);
contract
.instantiate_with_version(
deps.as_mut(),
&env,
&info_minter,
Cw721InstantiateMsg {
name: "collection_name".into(),
symbol: "collection_symbol".into(),
collection_info_extension: None,
creator: Some(addrs.addr(CREATOR_ADDR).into()),
minter: Some(addrs.addr(MINTER_ADDR).into()),
withdraw_address: None,
},
"contract_name",
"contract_version",
)
.unwrap();
let additional_minter1 = addrs.addr("additional_minter1");
let additional_minter2 = addrs.addr("additional_minter2");
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter1.to_string(),
},
)
.unwrap();
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::AddMinter {
minter: additional_minter2.to_string(),
},
)
.unwrap();
let new_owner = addrs.addr("new_owner");
contract
.execute(
deps.as_mut(),
&env,
&info_minter,
Cw721ExecuteMsg::UpdateMinterOwnership(Action::TransferOwnership {
new_owner: new_owner.to_string(),
expiry: Some(cw_utils::Expiration::AtHeight(100)),
}),
)
.unwrap();
env.block.height = 200;
let info_new_owner = addrs.info("new_owner");
let err = contract
.execute(
deps.as_mut(),
&env,
&info_new_owner,
Cw721ExecuteMsg::UpdateMinterOwnership(Action::AcceptOwnership),
)
.unwrap_err();
assert!(
matches!(
err,
Cw721ContractError::Ownership(OwnershipError::TransferExpired)
),
"expected TransferExpired, got: {err:?}"
);
let res =
contract
.query(
deps.as_ref(),
&env,
Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::GetAdditionalMinters {
start_after: None,
limit: None,
},
)
.unwrap();
let response: AdditionalMintersResponse = from_json(res).unwrap();
assert_eq!(response.minters.len(), 2);
}