// See https://github.com/paritytech/ink/tree/v2.1.0/examples/erc721 // Copyright 2019-2020 Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! # ERC721 //! //! This is an ERC721 Token implementation. //! //! ## Warning //! //! This contract is an *example*. It is neither audited nor endorsed for production use. //! Do **not** rely on it to keep anything of value secure. //! //! ## Overview //! //! This contract demonstrates how to build non-fungible or unique tokens using ink!. //! //! ## Error Handling //! //! Any function that modifies the state returns a Result type and does not changes the state //! if the Error occurs. //! The errors are defined as an Enum type. Any other error or invariant violation //! triggers a panic and therefore rolls back the transaction. //! //! ## Token Management //! //! After creating a new token, the function caller becomes the owner. //! A token can be created, transferred, or destroyed. //! //! Token owners can assign other accounts for transferring specific tokens on their behalf. //! It is also possible to authorize an operator (higher rights) for another account to handle tokens. //! //! ### Token Creation //! //! Token creation start by calling the `mint(&mut self, id: u32)` function. //! The token owner becomes the function caller. The Token ID needs to be specified //! as the argument on this function call. //! //! ### Token Transfer //! //! Transfers may be initiated by: //! - The owner of a token //! - The approved address of a token //! - An authorized operator of the current owner of a token //! //! The token owner can transfer a token by calling the `transfer` or `transfer_from` functions. //! An approved address can make a token transfer by calling the `transfer_from` funtion. //! Operators can transfer tokens on another account's behalf or can approve a token transfer //! for a different account. //! //! ### Token Removal //! //! Tokens can be destroyed by burning them. Only the token owner is allowed to burn a token. #![cfg_attr(not(feature = "std"), no_std)] use ink_lang as ink; #[ink::contract(version = "0.1.0")] mod erc721 { use ink_core::storage; use scale::{Decode, Encode}; /// A token ID. pub type TokenId = u32; #[ink(storage)] struct Erc721 { /// Mapping from token to owner. token_owner: storage::HashMap, /// Mapping from token to approvals users. token_approvals: storage::HashMap, /// Mapping from owner to number of owned token. owned_tokens_count: storage::HashMap, /// Mapping from owner to operator approvals. operator_approvals: storage::HashMap<(AccountId, AccountId), bool>, } #[derive(Encode, Decode, Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "ink-generate-abi", derive(type_metadata::Metadata))] pub enum Error { NotOwner, NotApproved, TokenExists, TokenNotFound, CannotInsert, CannotRemove, CannotFetchValue, NotAllowed, } /// Event emitted when a token transfer occurs. #[ink(event)] struct Transfer { #[ink(topic)] from: Option, #[ink(topic)] to: Option, #[ink(topic)] id: TokenId, } /// Event emited when a token approve occurs. #[ink(event)] struct Approval { #[ink(topic)] from: AccountId, #[ink(topic)] to: AccountId, #[ink(topic)] id: TokenId, } /// Event emitted when an operator is enabled or disabled for an owner. /// The operator can manage all NFTs of the owner. #[ink(event)] struct ApprovalForAll { #[ink(topic)] owner: AccountId, #[ink(topic)] operator: AccountId, approved: bool, } impl Erc721 { /// Creates a new ERC721 token contract. #[ink(constructor)] fn new(&mut self) {} /// Returns the balance of the owner. /// /// This represents the amount of unique tokens the owner has. #[ink(message)] fn balance_of(&self, owner: AccountId) -> u32 { self.balance_of_or_zero(&owner) } /// Returns the owner of the token. #[ink(message)] fn owner_of(&self, id: TokenId) -> Option { self.token_owner.get(&id).cloned() } /// Returns the approved account ID for this token if any. #[ink(message)] fn get_approved(&self, id: TokenId) -> Option { self.token_approvals.get(&id).cloned() } /// Returns `true` if the operator is approved by the owner. #[ink(message)] fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { self.approved_for_all(owner, operator) } /// Approves or disapproves the operator for all tokens of the caller. #[ink(message)] fn set_approval_for_all(&mut self, to: AccountId, approved: bool) -> Result<(), Error> { self.approve_for_all(to, approved)?; Ok(()) } /// Approves the account to transfer the specified token on behalf of the caller. #[ink(message)] fn approve(&mut self, to: AccountId, id: TokenId) -> Result<(), Error> { self.approve_for(&to, id)?; Ok(()) } /// Transfers the token from the caller to the given destination. #[ink(message)] fn transfer(&mut self, destination: AccountId, id: TokenId) -> Result<(), Error> { let caller = self.env().caller(); self.transfer_token_from(&caller, &destination, id)?; Ok(()) } /// Transfer approved or owned token. #[ink(message)] fn transfer_from( &mut self, from: AccountId, to: AccountId, id: TokenId, ) -> Result<(), Error> { self.transfer_token_from(&from, &to, id)?; Ok(()) } /// Creates a new token. #[ink(message)] fn mint(&mut self, id: TokenId) -> Result<(), Error> { let caller = self.env().caller(); self.add_token_to(&caller, id)?; self.env().emit_event(Transfer { from: Some(AccountId::from([0x0; 32])), to: Some(caller), id, }); Ok(()) } /// Deletes an existing token. Only the owner can burn the token. #[ink(message)] fn burn(&mut self, id: TokenId) -> Result<(), Error> { let caller = self.env().caller(); if self.token_owner.get(&id) != Some(&caller) { return Err(Error::NotOwner); }; self.remove_token_from(&caller, id)?; self.env().emit_event(Transfer { from: Some(caller), to: Some(AccountId::from([0x0; 32])), id, }); Ok(()) } /// Transfers token `id` `from` the sender to the `to` AccountId. fn transfer_token_from( &mut self, from: &AccountId, to: &AccountId, id: TokenId, ) -> Result<(), Error> { let caller = self.env().caller(); if !self.exists(id) { return Err(Error::TokenNotFound); }; if !self.approved_or_owner(Some(caller), id) { return Err(Error::NotApproved); }; self.clear_approval(id)?; self.remove_token_from(from, id)?; self.add_token_to(to, id)?; self.env().emit_event(Transfer { from: Some(*from), to: Some(*to), id, }); Ok(()) } /// Removes token `id` from the owner. fn remove_token_from(&mut self, from: &AccountId, id: TokenId) -> Result<(), Error> { if !self.exists(id) { return Err(Error::TokenNotFound); } self.decrease_counter_of(from)?; self.token_owner.remove(&id).ok_or(Error::CannotRemove)?; Ok(()) } /// Adds the token `id` to the `to` AccountID. fn add_token_to(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> { if self.exists(id) { return Err(Error::TokenExists); }; if *to == AccountId::from([0x0; 32]) { return Err(Error::NotAllowed); }; self.increase_counter_of(to)?; if self.token_owner.insert(id, *to).is_some() { return Err(Error::CannotInsert); } Ok(()) } /// Approves or disapproves the operator to transfer all tokens of the caller. fn approve_for_all(&mut self, to: AccountId, approved: bool) -> Result<(), Error> { let caller = self.env().caller(); if to == caller { return Err(Error::NotAllowed); } self.env().emit_event(ApprovalForAll { owner: caller, operator: to, approved, }); if self.approved_for_all(caller, to) { let status = self .operator_approvals .get_mut(&(caller, to)) .ok_or(Error::CannotFetchValue)?; *status = approved; Ok(()) } else { match self.operator_approvals.insert((caller, to), approved) { Some(_) => Err(Error::CannotInsert), None => Ok(()), } } } /// Approve the passed AccountId to transfer the specified token on behalf of the message's sender. fn approve_for(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> { let caller = self.env().caller(); let owner = self.owner_of(id); if !(owner == Some(caller) || self.approved_for_all(owner.expect("Error with AccountId"), caller)) { return Err(Error::NotAllowed); }; if *to == AccountId::from([0x0; 32]) { return Err(Error::NotAllowed); }; if self.token_approvals.insert(id, *to).is_some() { return Err(Error::CannotInsert); }; self.env().emit_event(Approval { from: caller, to: *to, id, }); Ok(()) } /// Increase token counter from the `of` AccountId. fn increase_counter_of(&mut self, of: &AccountId) -> Result<(), Error> { if self.balance_of_or_zero(of) > 0 { let count = self .owned_tokens_count .get_mut(of) .ok_or(Error::CannotFetchValue)?; *count += 1; Ok(()) } else { match self.owned_tokens_count.insert(*of, 1) { Some(_) => Err(Error::CannotInsert), None => Ok(()), } } } /// Decrease token counter from the `of` AccountId. fn decrease_counter_of(&mut self, of: &AccountId) -> Result<(), Error> { let count = self .owned_tokens_count .get_mut(of) .ok_or(Error::CannotFetchValue)?; *count -= 1; Ok(()) } /// Removes existing approval from token `id`. fn clear_approval(&mut self, id: TokenId) -> Result<(), Error> { if !self.token_approvals.contains_key(&id) { return Ok(()); }; match self.token_approvals.remove(&id) { Some(_res) => Ok(()), None => Err(Error::CannotRemove), } } // Returns the total number of tokens from an account. fn balance_of_or_zero(&self, of: &AccountId) -> u32 { *self.owned_tokens_count.get(of).unwrap_or(&0) } /// Gets an operator on other Account's behalf. fn approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { *self .operator_approvals .get(&(owner, operator)) .unwrap_or(&false) } /// Returns true if the AccountId `from` is the owner of token `id` /// or it has been approved on behalf of the token `id` owner. fn approved_or_owner(&self, from: Option, id: TokenId) -> bool { let owner = self.owner_of(id); from != Some(AccountId::from([0x0; 32])) && (from == owner || from == self.token_approvals.get(&id).cloned() || self.approved_for_all( owner.expect("Error with AccountId"), from.expect("Error with AccountId"), )) } /// Returns true if token `id` exists or false if it does not. fn exists(&self, id: TokenId) -> bool { self.token_owner.get(&id).is_some() && self.token_owner.contains_key(&id) } } /// Unit tests #[cfg(test)] mod tests { /// Imports all the definitions from the outer scope so we can use them here. use super::*; use ink_core::env; #[test] fn mint_works() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Token 1 does not exists. assert_eq!(erc721.owner_of(1), None); // Alice does not owns tokens. assert_eq!(erc721.balance_of(accounts.alice), 0); // Create token Id 1. assert_eq!(erc721.mint(1), Ok(())); // Alice owns 1 token. assert_eq!(erc721.balance_of(accounts.alice), 1); } #[test] fn mint_existing_should_fail() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Create token Id 1. assert_eq!(erc721.mint(1), Ok(())); // The first Transfer event takes place assert_eq!(1, env::test::recorded_events().count()); // Alice owns 1 token. assert_eq!(erc721.balance_of(accounts.alice), 1); // Alice owns token Id 1. assert_eq!(erc721.owner_of(1), Some(accounts.alice)); // Cannot create token Id if it exists. // Bob cannot own token Id 1. assert_eq!(erc721.mint(1), Err(Error::TokenExists)); } #[test] fn transfer_works() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Create token Id 1 for Alice assert_eq!(erc721.mint(1), Ok(())); // Alice owns token 1 assert_eq!(erc721.balance_of(accounts.alice), 1); // Bob does not owns any token assert_eq!(erc721.balance_of(accounts.bob), 0); // The first Transfer event takes place assert_eq!(1, env::test::recorded_events().count()); // Alice transfers token 1 to Bob assert_eq!(erc721.transfer(accounts.bob, 1), Ok(())); // The second Transfer event takes place assert_eq!(2, env::test::recorded_events().count()); // Bob owns token 1 assert_eq!(erc721.balance_of(accounts.bob), 1); } #[test] fn invalid_transfer_should_fail() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Transfer token fails if it does not exists. assert_eq!(erc721.transfer(accounts.bob, 2), Err(Error::TokenNotFound)); // Token Id 2 does not exists. assert_eq!(erc721.owner_of(2), None); // Create token Id 2. assert_eq!(erc721.mint(2), Ok(())); // Alice owns 1 token. assert_eq!(erc721.balance_of(accounts.alice), 1); // Token Id 2 is owned by Alice. assert_eq!(erc721.owner_of(2), Some(accounts.alice)); // Get contract address let callee = env::account_id::().unwrap_or([0x0; 32].into()); // Create call let mut data = env::call::CallData::new(env::call::Selector::from_str("balance_of")); data.push_arg(&accounts.bob); // Push the new execution context to set Bob as caller assert_eq!( env::test::push_execution_context::( accounts.bob, callee, 1000000, 1000000, data ), () ); // Bob cannot transfer not owned tokens. assert_eq!(erc721.transfer(accounts.eve, 2), Err(Error::NotApproved)); } #[test] fn approved_transfer_works() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Create token Id 1. assert_eq!(erc721.mint(1), Ok(())); // Token Id 1 is owned by Alice. assert_eq!(erc721.owner_of(1), Some(accounts.alice)); // Approve token Id 1 transfer for Bob on behalf of Alice. assert_eq!(erc721.approve(accounts.bob, 1), Ok(())); // Get contract address. let callee = env::account_id::().unwrap_or([0x0; 32].into()); // Create call let mut data = env::call::CallData::new(env::call::Selector::from_str("balance_of")); data.push_arg(&accounts.bob); // Push the new execution context to set Bob as caller assert_eq!( env::test::push_execution_context::( accounts.bob, callee, 1000000, 1000000, data ), () ); // Bob transfers token Id 1 from Alice to Eve. assert_eq!( erc721.transfer_from(accounts.alice, accounts.eve, 1), Ok(()) ); // TokenId 3 is owned by Eve. assert_eq!(erc721.owner_of(1), Some(accounts.eve)); // Alice does not owns tokens. assert_eq!(erc721.balance_of(accounts.alice), 0); // Bob does not owns tokens. assert_eq!(erc721.balance_of(accounts.bob), 0); // Eve owns 1 token. assert_eq!(erc721.balance_of(accounts.eve), 1); } #[test] fn approved_for_all_works() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Create token Id 1. assert_eq!(erc721.mint(1), Ok(())); // Create token Id 2. assert_eq!(erc721.mint(2), Ok(())); // Alice owns 2 tokens. assert_eq!(erc721.balance_of(accounts.alice), 2); // Approve token Id 1 transfer for Bob on behalf of Alice. assert_eq!(erc721.set_approval_for_all(accounts.bob, true), Ok(())); // Bob is an approved operator for Alice assert_eq!( erc721.is_approved_for_all(accounts.alice, accounts.bob), true ); // Get contract address. let callee = env::account_id::().unwrap_or([0x0; 32].into()); // Create call let mut data = env::call::CallData::new(env::call::Selector::from_str("balance_of")); data.push_arg(&accounts.bob); // Push the new execution context to set Bob as caller assert_eq!( env::test::push_execution_context::( accounts.bob, callee, 1000000, 1000000, data ), () ); // Bob transfers token Id 1 from Alice to Eve. assert_eq!( erc721.transfer_from(accounts.alice, accounts.eve, 1), Ok(()) ); // TokenId 1 is owned by Eve. assert_eq!(erc721.owner_of(1), Some(accounts.eve)); // Alice owns 1 token. assert_eq!(erc721.balance_of(accounts.alice), 1); // Bob transfers token Id 2 from Alice to Eve. assert_eq!( erc721.transfer_from(accounts.alice, accounts.eve, 2), Ok(()) ); // Bob does not owns tokens. assert_eq!(erc721.balance_of(accounts.bob), 0); // Eve owns 2 tokens. assert_eq!(erc721.balance_of(accounts.eve), 2); // Get back to the parent execution context. env::test::pop_execution_context(); // Remove operator approval for Bob on behalf of Alice. assert_eq!(erc721.set_approval_for_all(accounts.bob, false), Ok(())); // Bob is not an approved operator for Alice. assert_eq!( erc721.is_approved_for_all(accounts.alice, accounts.bob), false ); } #[test] fn not_approved_transfer_should_fail() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Create token Id 1. assert_eq!(erc721.mint(1), Ok(())); // Alice owns 1 token. assert_eq!(erc721.balance_of(accounts.alice), 1); // Bob does not owns tokens. assert_eq!(erc721.balance_of(accounts.bob), 0); // Eve does not owns tokens. assert_eq!(erc721.balance_of(accounts.eve), 0); // Get contract address. let callee = env::account_id::().unwrap_or([0x0; 32].into()); // Create call let mut data = env::call::CallData::new(env::call::Selector::from_str("balance_of")); data.push_arg(&accounts.bob); // Push the new execution context to set Eve as caller assert_eq!( env::test::push_execution_context::( accounts.eve, callee, 1000000, 1000000, data ), () ); // Eve is not an approved operator by Alice. assert_eq!( erc721.transfer_from(accounts.alice, accounts.frank, 1), Err(Error::NotApproved) ); // Alice owns 1 token. assert_eq!(erc721.balance_of(accounts.alice), 1); // Bob does not owns tokens. assert_eq!(erc721.balance_of(accounts.bob), 0); // Eve does not owns tokens. assert_eq!(erc721.balance_of(accounts.eve), 0); } #[test] fn burn_works() { let accounts = env::test::default_accounts::().expect("Cannot get accounts"); // Create a new contract instance. let mut erc721 = Erc721::new(); // Create token Id 1 for Alice assert_eq!(erc721.mint(1), Ok(())); // Alice owns 1 token. assert_eq!(erc721.balance_of(accounts.alice), 1); // Alice owns token Id 1. assert_eq!(erc721.owner_of(1), Some(accounts.alice)); // Destroy token Id 1. assert_eq!(erc721.burn(1), Ok(())); // Alice does not owns tokens. assert_eq!(erc721.balance_of(accounts.alice), 0); // Token Id 1 does not exists assert_eq!(erc721.owner_of(1), None); } } }