/// Inserts a suite of ink! unit tests intended for a contract implementing PSP34 trait. /// `$contract` argument should be the name of the contract struct. /// `$constructor` argument should be the name of a function, which initializes `$contract`. /// This macro should be invoked inside `#[ink::contract]` module. #[macro_export] macro_rules! tests { ($contract:ident, $constructor:expr) => { mod psp34_unit_tests { use super::super::*; use ink::env::{test::*, DefaultEnvironment as E}; type Event = <$contract as ::ink::reflect::ContractEventBase>::Type; // Gathers all emitted events, skip `shift` first, decode the rest and return as vector fn decode_events(shift: usize) -> Vec { recorded_events() .skip(shift) .map(|e| ::decode(&mut &e.data[..]).unwrap()) .collect() } // Asserts if the given event is a Transfer with particular from_, to_ and value_ fn assert_transfer(event: &Event, from_: AccountId, to_: AccountId, id_: Id) { if let Event::Transfer(Transfer { from, to, id }) = event { assert_eq!(*from, Some(from_), "Transfer event: 'from' mismatch"); assert_eq!(*to, Some(to_), "Transfer event: 'to' mismatch"); assert_eq!(*id, id_, "Transfer event: 'id' mismatch"); } else { panic!("Event is not Transfer") } } // Asserts if the given event is a Approval with particular owner_, spender_ and amount_ fn assert_approval( event: &Event, owner_: AccountId, operator_: AccountId, id_: Option, approved_ : bool, ) { if let Event::Approval(Approval { owner, operator, id, approved, }) = event { assert_eq!(*owner, owner_, "Approval event: 'owner' mismatch"); assert_eq!(*operator, operator_, "Approval event: 'operator' mismatch"); assert_eq!(*id, id_, "Approval event: 'id' mismatch"); assert_eq!(*approved, approved_, "Approval event: 'approved' mismatch") } else { panic!("Event is not Approval") } } fn set_caller(sender: AccountId) { ink::env::test::set_caller::(sender); } #[ink::test] fn mint_works() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Token 1 does not exists. assert_eq!(token.owner_of(Id::U8(1)), None); // Alice does not owns tokens. assert_eq!(token.balance_of(accounts.alice), 0); // Create token Id 1. assert_eq!(token.mint(Id::U8(1)), Ok(())); // Alice owns 1 token. assert_eq!(token.balance_of(accounts.alice), 1); } #[ink::test] fn mint_existing_should_fail() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1. assert_eq!(token.mint(Id::U8(1)), Ok(())); // The first Transfer event takes place assert_eq!(1, recorded_events().count()); // Alice owns 1 token. assert_eq!(token.balance_of(accounts.alice), 1); // Alice owns token Id 1. assert_eq!(token.owner_of(Id::U8(1)), Some(accounts.alice)); // Cannot create token Id if it exists. // Bob cannot own token Id 1. assert_eq!(token.mint(Id::U8(1)), Err(PSP34Error::TokenExists)); } #[ink::test] fn transfer_works() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1 for Alice assert_eq!(token.mint(Id::U8(1)), Ok(())); // Alice owns token 1 assert_eq!(token.balance_of(accounts.alice), 1); // Bob does not owns any token assert_eq!(token.balance_of(accounts.bob), 0); // The first Transfer event takes place assert_eq!(1, recorded_events().count()); // Alice transfers token 1 to Bob assert_eq!( token.transfer(accounts.bob, Id::U8(1), vec![u8::default()]), Ok(()) ); // The second Transfer event takes place assert_eq!(2, recorded_events().count()); // Bob owns token 1 assert_eq!(token.balance_of(accounts.bob), 1); } #[ink::test] fn transfer_emits_event() { let accounts = default_accounts::(); let start = recorded_events().count(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1 for Alice assert_eq!(token.mint(Id::U8(1)), Ok(())); // Alice owns token 1 assert_eq!(token.balance_of(accounts.alice), 1); // Bob does not owns any token assert_eq!(token.balance_of(accounts.bob), 0); // The first Transfer event takes place assert_eq!(1, recorded_events().count()); // Alice transfers token 1 to Bob assert_eq!( token.transfer(accounts.bob, Id::U8(1), vec![u8::default()]), Ok(()) ); // The second Transfer event takes place assert_eq!(2, recorded_events().count()); // The correct event emited let events = decode_events(start); assert_transfer(&events[1], accounts.alice, accounts.bob, Id::U8(1)); } #[ink::test] fn invalid_transfer_should_fail() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Transfer token fails if it does not exists. assert_eq!( token.transfer(accounts.bob, Id::U8(2), vec![u8::default()]), Err(PSP34Error::TokenNotExists) ); // Token Id 2 does not exists. assert_eq!(token.owner_of(Id::U8(2)), None); // Create token Id 2. assert_eq!(token.mint(Id::U8(2)), Ok(())); // Alice owns 1 token. assert_eq!(token.balance_of(accounts.alice), 1); // Token Id 2 is owned by Alice. assert_eq!(token.owner_of(Id::U8(2)), Some(accounts.alice)); // Set Bob as caller set_caller(accounts.bob); // Bob cannot transfer not owned tokens. assert_eq!( token.transfer(accounts.eve, Id::U8(2), vec![u8::default()]), Err(PSP34Error::NotApproved) ); } #[ink::test] fn approved_transfer_works() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1. assert_eq!(token.mint(Id::U8(1)), Ok(())); // Token Id 1 is owned by Alice. assert_eq!(token.owner_of(Id::U8(1)), Some(accounts.alice)); // Approve token Id 1 transfer for Bob on behalf of Alice. assert_eq!(token.approve(accounts.bob, Some(Id::U8(1)), true), Ok(())); // Set Bob as caller set_caller(accounts.bob); // Bob transfers token Id 1 from Alice to Eve. assert_eq!( token.transfer(accounts.eve, Id::U8(1), vec![u8::default()]), Ok(()) ); // TokenId 3 is owned by Eve. assert_eq!(token.owner_of(Id::U8(1)), Some(accounts.eve)); // Alice does not owns tokens. assert_eq!(token.balance_of(accounts.alice), 0); // Bob does not owns tokens. assert_eq!(token.balance_of(accounts.bob), 0); // Eve owns 1 token. assert_eq!(token.balance_of(accounts.eve), 1); } #[ink::test] fn approve_emits_event() { let accounts = default_accounts::(); let start = recorded_events().count(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1. assert_eq!(token.mint(Id::U8(1)), Ok(())); // Token Id 1 is owned by Alice. assert_eq!(token.owner_of(Id::U8(1)), Some(accounts.alice)); // Approve token Id 1 transfer for Bob on behalf of Alice. assert_eq!(token.approve(accounts.bob, Some(Id::U8(1)), true), Ok(())); // The event approve event takes place let events = decode_events(start); assert_eq!(events.len(), 2); assert_approval(&events[1], accounts.alice, accounts.bob, Some(Id::U8(1)), true); } #[ink::test] fn approved_for_all_works() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1. assert_eq!(token.mint(Id::U8(1)), Ok(())); // Create token Id 2. assert_eq!(token.mint(Id::U8(2)), Ok(())); // Alice owns 2 tokens. assert_eq!(token.balance_of(accounts.alice), 2); // Approve all tokens transfer for Bob on behalf of Alice. assert_eq!(token.approve(accounts.bob, None, true), Ok(())); // Bob is an approved operator for Alice assert!(token.allowance(accounts.alice, accounts.bob, None)); // Set Bob as caller set_caller(accounts.bob); // Bob transfers token Id 1 from Alice to Eve. assert_eq!( token.transfer(accounts.eve, Id::U8(1), vec![u8::default()]), Ok(()) ); // TokenId 1 is owned by Eve. assert_eq!(token.owner_of(Id::U8(1)), Some(accounts.eve)); // Alice owns 1 token. assert_eq!(token.balance_of(accounts.alice), 1); // Bob transfers token Id 2 from Alice to Eve. assert_eq!( token.transfer(accounts.eve, Id::U8(2), vec![u8::default()]), Ok(()) ); // Bob does not own tokens. assert_eq!(token.balance_of(accounts.bob), 0); // Eve owns 2 tokens. assert_eq!(token.balance_of(accounts.eve), 2); // Remove operator approval for Bob on behalf of Alice. set_caller(accounts.alice); assert_eq!(token.approve(accounts.bob, None, false), Ok(())); // Bob is not an approved operator for Alice. assert!(!token.allowance(accounts.alice, accounts.bob, None)); } #[ink::test] fn approved_for_all_revoke_single_approval_should_fail() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1. assert_eq!(token.mint(Id::U8(1)), Ok(())); // Create token Id 2. assert_eq!(token.mint(Id::U8(2)), Ok(())); // Alice owns 2 tokens. assert_eq!(token.balance_of(accounts.alice), 2); // Approve all tokens transfer for Bob on behalf of Alice. assert_eq!(token.approve(accounts.bob, None, true), Ok(())); // Bob is an approved operator for Alice assert!(token.allowance(accounts.alice, accounts.bob, None)); // Cannot revoke approval for a single token for Bob assert_eq!(token.approve(accounts.bob, Some(Id::U8(1)), false), Err(PSP34Error::Custom(String::from( "Cannot revoke approval for a single token, when the operator has approval for all tokens."))) ); } #[ink::test] fn not_approved_transfer_should_fail() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1. assert_eq!(token.mint(Id::U8(1)), Ok(())); // Alice owns 1 token. assert_eq!(token.balance_of(accounts.alice), 1); // Bob does not owns tokens. assert_eq!(token.balance_of(accounts.bob), 0); // Eve does not owns tokens. assert_eq!(token.balance_of(accounts.eve), 0); // Set Eve as caller set_caller(accounts.eve); // Eve is not an approved operator by Alice. assert_eq!( token.transfer(accounts.frank, Id::U8(1), vec![u8::default()]), Err(PSP34Error::NotApproved) ); // Alice owns 1 token. assert_eq!(token.balance_of(accounts.alice), 1); // Bob does not owns tokens. assert_eq!(token.balance_of(accounts.bob), 0); // Eve does not owns tokens. assert_eq!(token.balance_of(accounts.eve), 0); } #[ink::test] fn burn_works() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1 for Alice assert_eq!(token.mint(Id::U8(1)), Ok(())); // Alice owns 1 token. assert_eq!(token.balance_of(accounts.alice), 1); // Alice owns token Id 1. assert_eq!(token.owner_of(Id::U8(1)), Some(accounts.alice)); // Destroy token Id 1. assert_eq!(token.burn(accounts.alice, Id::U8(1)), Ok(())); // Alice does not owns tokens. assert_eq!(token.balance_of(accounts.alice), 0); // Token Id 1 does not exists assert_eq!(token.owner_of(Id::U8(1)), None); } #[ink::test] fn burn_fails_token_not_found() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Try burning a non existent token assert_eq!( token.burn(accounts.alice, Id::U8(1)), Err(PSP34Error::TokenNotExists) ); } #[ink::test] fn burn_fails_not_owner() { let accounts = default_accounts::(); // Create a new contract instance. let mut token = $constructor(); // Create token Id 1 for Alice assert_eq!(token.mint(Id::U8(1)), Ok(())); // Try burning this token with a different account set_caller(accounts.eve); assert_eq!( token.burn(accounts.alice, Id::U8(1)), Err(PSP34Error::NotApproved) ); } } }; }