wormhole/terra/contracts/cw721-base/src/contract_tests.rs

735 lines
21 KiB
Rust

#![cfg(test)]
use cosmwasm_std::{
from_binary,
testing::{mock_dependencies, mock_env, mock_info},
to_binary, CosmosMsg, DepsMut, Empty, Response, WasmMsg,
};
use cw721::{
Approval, ApprovalResponse, ContractInfoResponse, Cw721Query, Cw721ReceiveMsg, Expiration,
NftInfoResponse, OperatorsResponse, OwnerOfResponse,
};
use crate::{
ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg, MintMsg, QueryMsg,
};
const MINTER: &str = "merlin";
const CONTRACT_NAME: &str = "Magic Power";
const SYMBOL: &str = "MGK";
fn setup_contract(deps: DepsMut<'_>) -> Cw721Contract<'static, Extension, Empty> {
let contract = Cw721Contract::default();
let msg = InstantiateMsg {
name: CONTRACT_NAME.to_string(),
symbol: SYMBOL.to_string(),
minter: String::from(MINTER),
};
let info = mock_info("creator", &[]);
let res = contract.instantiate(deps, mock_env(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
contract
}
#[test]
fn proper_instantiation() {
let mut deps = mock_dependencies(&[]);
let contract = Cw721Contract::<Extension, Empty>::default();
let msg = InstantiateMsg {
name: CONTRACT_NAME.to_string(),
symbol: SYMBOL.to_string(),
minter: String::from(MINTER),
};
let info = mock_info("creator", &[]);
// we can just call .unwrap() to assert this was a success
let res = contract
.instantiate(deps.as_mut(), mock_env(), info, msg)
.unwrap();
assert_eq!(0, res.messages.len());
// it worked, let's query the state
let res = contract.minter(deps.as_ref()).unwrap();
assert_eq!(MINTER, res.minter);
let info = contract.contract_info(deps.as_ref()).unwrap();
assert_eq!(
info,
ContractInfoResponse {
name: CONTRACT_NAME.to_string(),
symbol: SYMBOL.to_string(),
}
);
let count = contract.num_tokens(deps.as_ref()).unwrap();
assert_eq!(0, count.count);
// list the token_ids
let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap();
assert_eq!(0, tokens.tokens.len());
}
#[test]
fn minting() {
let mut deps = mock_dependencies(&[]);
let contract = setup_contract(deps.as_mut());
let token_id = "petrify".to_string();
let token_uri = "https://www.merriam-webster.com/dictionary/petrify".to_string();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id.clone(),
owner: String::from("medusa"),
token_uri: Some(token_uri.clone()),
extension: None,
});
// random cannot mint
let random = mock_info("random", &[]);
let err = contract
.execute(deps.as_mut(), mock_env(), random, mint_msg.clone())
.unwrap_err();
assert_eq!(err, ContractError::Unauthorized {});
// minter can mint
let allowed = mock_info(MINTER, &[]);
let _ = contract
.execute(deps.as_mut(), mock_env(), allowed, mint_msg)
.unwrap();
// ensure num tokens increases
let count = contract.num_tokens(deps.as_ref()).unwrap();
assert_eq!(1, count.count);
// unknown nft returns error
let _ = contract
.nft_info(deps.as_ref(), "unknown".to_string())
.unwrap_err();
// this nft info is correct
let info = contract.nft_info(deps.as_ref(), token_id.clone()).unwrap();
assert_eq!(
info,
NftInfoResponse::<Extension> {
token_uri: Some(token_uri),
extension: None,
}
);
// owner info is correct
let owner = contract
.owner_of(deps.as_ref(), mock_env(), token_id.clone(), true)
.unwrap();
assert_eq!(
owner,
OwnerOfResponse {
owner: String::from("medusa"),
approvals: vec![],
}
);
// Cannot mint same token_id again
let mint_msg2 = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id.clone(),
owner: String::from("hercules"),
token_uri: None,
extension: None,
});
let allowed = mock_info(MINTER, &[]);
let err = contract
.execute(deps.as_mut(), mock_env(), allowed, mint_msg2)
.unwrap_err();
assert_eq!(err, ContractError::Claimed {});
// list the token_ids
let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap();
assert_eq!(1, tokens.tokens.len());
assert_eq!(vec![token_id], tokens.tokens);
}
#[test]
fn burning() {
let mut deps = mock_dependencies(&[]);
let contract = setup_contract(deps.as_mut());
let token_id = "petrify".to_string();
let token_uri = "https://www.merriam-webster.com/dictionary/petrify".to_string();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id.clone(),
owner: MINTER.to_string(),
token_uri: Some(token_uri),
extension: None,
});
let burn_msg = ExecuteMsg::Burn { token_id };
// mint some NFT
let allowed = mock_info(MINTER, &[]);
let _ = contract
.execute(deps.as_mut(), mock_env(), allowed.clone(), mint_msg)
.unwrap();
// random not allowed to burn
let random = mock_info("random", &[]);
let err = contract
.execute(deps.as_mut(), mock_env(), random, burn_msg.clone())
.unwrap_err();
assert_eq!(err, ContractError::Unauthorized {});
let _ = contract
.execute(deps.as_mut(), mock_env(), allowed, burn_msg)
.unwrap();
// ensure num tokens decreases
let count = contract.num_tokens(deps.as_ref()).unwrap();
assert_eq!(0, count.count);
// trying to get nft returns error
let _ = contract
.nft_info(deps.as_ref(), "petrify".to_string())
.unwrap_err();
// list the token_ids
let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap();
assert!(tokens.tokens.is_empty());
}
#[test]
fn transferring_nft() {
let mut deps = mock_dependencies(&[]);
let contract = setup_contract(deps.as_mut());
// Mint a token
let token_id = "melt".to_string();
let token_uri = "https://www.merriam-webster.com/dictionary/melt".to_string();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id.clone(),
owner: String::from("venus"),
token_uri: Some(token_uri),
extension: None,
});
let minter = mock_info(MINTER, &[]);
contract
.execute(deps.as_mut(), mock_env(), minter, mint_msg)
.unwrap();
// random cannot transfer
let random = mock_info("random", &[]);
let transfer_msg = ExecuteMsg::TransferNft {
recipient: String::from("random"),
token_id: token_id.clone(),
};
let err = contract
.execute(deps.as_mut(), mock_env(), random, transfer_msg)
.unwrap_err();
assert_eq!(err, ContractError::Unauthorized {});
// owner can
let random = mock_info("venus", &[]);
let transfer_msg = ExecuteMsg::TransferNft {
recipient: String::from("random"),
token_id: token_id.clone(),
};
let res = contract
.execute(deps.as_mut(), mock_env(), random, transfer_msg)
.unwrap();
assert_eq!(
res,
Response::new()
.add_attribute("action", "transfer_nft")
.add_attribute("sender", "venus")
.add_attribute("recipient", "random")
.add_attribute("token_id", token_id)
);
}
#[test]
fn sending_nft() {
let mut deps = mock_dependencies(&[]);
let contract = setup_contract(deps.as_mut());
// Mint a token
let token_id = "melt".to_string();
let token_uri = "https://www.merriam-webster.com/dictionary/melt".to_string();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id.clone(),
owner: String::from("venus"),
token_uri: Some(token_uri),
extension: None,
});
let minter = mock_info(MINTER, &[]);
contract
.execute(deps.as_mut(), mock_env(), minter, mint_msg)
.unwrap();
let msg = to_binary("You now have the melting power").unwrap();
let target = String::from("another_contract");
let send_msg = ExecuteMsg::SendNft {
contract: target.clone(),
token_id: token_id.clone(),
msg: msg.clone(),
};
let random = mock_info("random", &[]);
let err = contract
.execute(deps.as_mut(), mock_env(), random, send_msg.clone())
.unwrap_err();
assert_eq!(err, ContractError::Unauthorized {});
// but owner can
let random = mock_info("venus", &[]);
let res = contract
.execute(deps.as_mut(), mock_env(), random, send_msg)
.unwrap();
let payload = Cw721ReceiveMsg {
sender: String::from("venus"),
token_id: token_id.clone(),
msg,
};
let expected = payload.into_cosmos_msg(target.clone()).unwrap();
// ensure expected serializes as we think it should
match &expected {
CosmosMsg::Wasm(WasmMsg::Execute { contract_addr, .. }) => {
assert_eq!(contract_addr, &target)
}
m => panic!("Unexpected message type: {:?}", m),
}
// and make sure this is the request sent by the contract
assert_eq!(
res,
Response::new()
.add_message(expected)
.add_attribute("action", "send_nft")
.add_attribute("sender", "venus")
.add_attribute("recipient", "another_contract")
.add_attribute("token_id", token_id)
);
}
#[test]
fn approving_revoking() {
let mut deps = mock_dependencies(&[]);
let contract = setup_contract(deps.as_mut());
// Mint a token
let token_id = "grow".to_string();
let token_uri = "https://www.merriam-webster.com/dictionary/grow".to_string();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id.clone(),
owner: String::from("demeter"),
token_uri: Some(token_uri),
extension: None,
});
let minter = mock_info(MINTER, &[]);
contract
.execute(deps.as_mut(), mock_env(), minter, mint_msg)
.unwrap();
// Give random transferring power
let approve_msg = ExecuteMsg::Approve {
spender: String::from("random"),
token_id: token_id.clone(),
expires: None,
};
let owner = mock_info("demeter", &[]);
let res = contract
.execute(deps.as_mut(), mock_env(), owner, approve_msg)
.unwrap();
assert_eq!(
res,
Response::new()
.add_attribute("action", "approve")
.add_attribute("sender", "demeter")
.add_attribute("spender", "random")
.add_attribute("token_id", token_id.clone())
);
// test approval query
let res = contract
.approval(
deps.as_ref(),
mock_env(),
token_id.clone(),
String::from("random"),
true,
)
.unwrap();
assert_eq!(
res,
ApprovalResponse {
approval: Approval {
spender: String::from("random"),
expires: Expiration::Never {}
}
}
);
// random can now transfer
let random = mock_info("random", &[]);
let transfer_msg = ExecuteMsg::TransferNft {
recipient: String::from("person"),
token_id: token_id.clone(),
};
contract
.execute(deps.as_mut(), mock_env(), random, transfer_msg)
.unwrap();
// Approvals are removed / cleared
let query_msg = QueryMsg::OwnerOf {
token_id: token_id.clone(),
include_expired: None,
};
let res: OwnerOfResponse = from_binary(
&contract
.query(deps.as_ref(), mock_env(), query_msg.clone())
.unwrap(),
)
.unwrap();
assert_eq!(
res,
OwnerOfResponse {
owner: String::from("person"),
approvals: vec![],
}
);
// Approve, revoke, and check for empty, to test revoke
let approve_msg = ExecuteMsg::Approve {
spender: String::from("random"),
token_id: token_id.clone(),
expires: None,
};
let owner = mock_info("person", &[]);
contract
.execute(deps.as_mut(), mock_env(), owner.clone(), approve_msg)
.unwrap();
let revoke_msg = ExecuteMsg::Revoke {
spender: String::from("random"),
token_id,
};
contract
.execute(deps.as_mut(), mock_env(), owner, revoke_msg)
.unwrap();
// Approvals are now removed / cleared
let res: OwnerOfResponse = from_binary(
&contract
.query(deps.as_ref(), mock_env(), query_msg)
.unwrap(),
)
.unwrap();
assert_eq!(
res,
OwnerOfResponse {
owner: String::from("person"),
approvals: vec![],
}
);
}
#[test]
fn approving_all_revoking_all() {
let mut deps = mock_dependencies(&[]);
let contract = setup_contract(deps.as_mut());
// Mint a couple tokens (from the same owner)
let token_id1 = "grow1".to_string();
let token_uri1 = "https://www.merriam-webster.com/dictionary/grow1".to_string();
let token_id2 = "grow2".to_string();
let token_uri2 = "https://www.merriam-webster.com/dictionary/grow2".to_string();
let mint_msg1 = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id1.clone(),
owner: String::from("demeter"),
token_uri: Some(token_uri1),
extension: None,
});
let minter = mock_info(MINTER, &[]);
contract
.execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg1)
.unwrap();
let mint_msg2 = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id2.clone(),
owner: String::from("demeter"),
token_uri: Some(token_uri2),
extension: None,
});
contract
.execute(deps.as_mut(), mock_env(), minter, mint_msg2)
.unwrap();
// paginate the token_ids
let tokens = contract.all_tokens(deps.as_ref(), None, Some(1)).unwrap();
assert_eq!(1, tokens.tokens.len());
assert_eq!(vec![token_id1.clone()], tokens.tokens);
let tokens = contract
.all_tokens(deps.as_ref(), Some(token_id1.clone()), Some(3))
.unwrap();
assert_eq!(1, tokens.tokens.len());
assert_eq!(vec![token_id2.clone()], tokens.tokens);
// demeter gives random full (operator) power over her tokens
let approve_all_msg = ExecuteMsg::ApproveAll {
operator: String::from("random"),
expires: None,
};
let owner = mock_info("demeter", &[]);
let res = contract
.execute(deps.as_mut(), mock_env(), owner, approve_all_msg)
.unwrap();
assert_eq!(
res,
Response::new()
.add_attribute("action", "approve_all")
.add_attribute("sender", "demeter")
.add_attribute("operator", "random")
);
// random can now transfer
let random = mock_info("random", &[]);
let transfer_msg = ExecuteMsg::TransferNft {
recipient: String::from("person"),
token_id: token_id1,
};
contract
.execute(deps.as_mut(), mock_env(), random.clone(), transfer_msg)
.unwrap();
// random can now send
let inner_msg = WasmMsg::Execute {
contract_addr: "another_contract".into(),
msg: to_binary("You now also have the growing power").unwrap(),
funds: vec![],
};
let msg: CosmosMsg = CosmosMsg::Wasm(inner_msg);
let send_msg = ExecuteMsg::SendNft {
contract: String::from("another_contract"),
token_id: token_id2,
msg: to_binary(&msg).unwrap(),
};
contract
.execute(deps.as_mut(), mock_env(), random, send_msg)
.unwrap();
// Approve_all, revoke_all, and check for empty, to test revoke_all
let approve_all_msg = ExecuteMsg::ApproveAll {
operator: String::from("operator"),
expires: None,
};
// person is now the owner of the tokens
let owner = mock_info("person", &[]);
contract
.execute(deps.as_mut(), mock_env(), owner, approve_all_msg)
.unwrap();
let res = contract
.operators(
deps.as_ref(),
mock_env(),
String::from("person"),
true,
None,
None,
)
.unwrap();
assert_eq!(
res,
OperatorsResponse {
operators: vec![cw721::Approval {
spender: String::from("operator"),
expires: Expiration::Never {}
}]
}
);
// second approval
let buddy_expires = Expiration::AtHeight(1234567);
let approve_all_msg = ExecuteMsg::ApproveAll {
operator: String::from("buddy"),
expires: Some(buddy_expires),
};
let owner = mock_info("person", &[]);
contract
.execute(deps.as_mut(), mock_env(), owner.clone(), approve_all_msg)
.unwrap();
// and paginate queries
let res = contract
.operators(
deps.as_ref(),
mock_env(),
String::from("person"),
true,
None,
Some(1),
)
.unwrap();
assert_eq!(
res,
OperatorsResponse {
operators: vec![cw721::Approval {
spender: String::from("buddy"),
expires: buddy_expires,
}]
}
);
let res = contract
.operators(
deps.as_ref(),
mock_env(),
String::from("person"),
true,
Some(String::from("buddy")),
Some(2),
)
.unwrap();
assert_eq!(
res,
OperatorsResponse {
operators: vec![cw721::Approval {
spender: String::from("operator"),
expires: Expiration::Never {}
}]
}
);
let revoke_all_msg = ExecuteMsg::RevokeAll {
operator: String::from("operator"),
};
contract
.execute(deps.as_mut(), mock_env(), owner, revoke_all_msg)
.unwrap();
// Approvals are removed / cleared without affecting others
let res = contract
.operators(
deps.as_ref(),
mock_env(),
String::from("person"),
false,
None,
None,
)
.unwrap();
assert_eq!(
res,
OperatorsResponse {
operators: vec![cw721::Approval {
spender: String::from("buddy"),
expires: buddy_expires,
}]
}
);
// ensure the filter works (nothing should be here
let mut late_env = mock_env();
late_env.block.height = 1234568; //expired
let res = contract
.operators(
deps.as_ref(),
late_env,
String::from("person"),
false,
None,
None,
)
.unwrap();
assert_eq!(0, res.operators.len());
}
#[test]
fn query_tokens_by_owner() {
let mut deps = mock_dependencies(&[]);
let contract = setup_contract(deps.as_mut());
let minter = mock_info(MINTER, &[]);
// Mint a couple tokens (from the same owner)
let token_id1 = "grow1".to_string();
let demeter = String::from("demeter");
let token_id2 = "grow2".to_string();
let ceres = String::from("ceres");
let token_id3 = "sing".to_string();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id1.clone(),
owner: demeter.clone(),
token_uri: None,
extension: None,
});
contract
.execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg)
.unwrap();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id2.clone(),
owner: ceres.clone(),
token_uri: None,
extension: None,
});
contract
.execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg)
.unwrap();
let mint_msg = ExecuteMsg::Mint(MintMsg::<Extension> {
token_id: token_id3.clone(),
owner: demeter.clone(),
token_uri: None,
extension: None,
});
contract
.execute(deps.as_mut(), mock_env(), minter, mint_msg)
.unwrap();
// get all tokens in order:
let expected = vec![token_id1.clone(), token_id2.clone(), token_id3.clone()];
let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap();
assert_eq!(&expected, &tokens.tokens);
// paginate
let tokens = contract.all_tokens(deps.as_ref(), None, Some(2)).unwrap();
assert_eq!(&expected[..2], &tokens.tokens[..]);
let tokens = contract
.all_tokens(deps.as_ref(), Some(expected[1].clone()), None)
.unwrap();
assert_eq!(&expected[2..], &tokens.tokens[..]);
// get by owner
let by_ceres = vec![token_id2];
let by_demeter = vec![token_id1, token_id3];
// all tokens by owner
let tokens = contract
.tokens(deps.as_ref(), demeter.clone(), None, None)
.unwrap();
assert_eq!(&by_demeter, &tokens.tokens);
let tokens = contract.tokens(deps.as_ref(), ceres, None, None).unwrap();
assert_eq!(&by_ceres, &tokens.tokens);
// paginate for demeter
let tokens = contract
.tokens(deps.as_ref(), demeter.clone(), None, Some(1))
.unwrap();
assert_eq!(&by_demeter[..1], &tokens.tokens[..]);
let tokens = contract
.tokens(deps.as_ref(), demeter, Some(by_demeter[0].clone()), Some(3))
.unwrap();
assert_eq!(&by_demeter[1..], &tokens.tokens[..]);
}