#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod erc721 {
use ink_storage::{
traits::SpreadAllocate,
Mapping,
};
use scale::{
Decode,
Encode,
};
pub type TokenId = u32;
#[ink(storage)]
#[derive(Default, SpreadAllocate)]
pub struct Erc721 {
token_owner: Mapping<TokenId, AccountId>,
token_approvals: Mapping<TokenId, AccountId>,
owned_tokens_count: Mapping<AccountId, u32>,
operator_approvals: Mapping<(AccountId, AccountId), ()>,
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
NotOwner,
NotApproved,
TokenExists,
TokenNotFound,
CannotInsert,
CannotFetchValue,
NotAllowed,
}
#[ink(event)]
pub struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
#[ink(topic)]
id: TokenId,
}
#[ink(event)]
pub struct Approval {
#[ink(topic)]
from: AccountId,
#[ink(topic)]
to: AccountId,
#[ink(topic)]
id: TokenId,
}
#[ink(event)]
pub struct ApprovalForAll {
#[ink(topic)]
owner: AccountId,
#[ink(topic)]
operator: AccountId,
approved: bool,
}
impl Erc721 {
#[ink(constructor)]
pub fn new() -> Self {
ink_lang::utils::initialize_contract(|_| {})
}
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> u32 {
self.balance_of_or_zero(&owner)
}
#[ink(message)]
pub fn owner_of(&self, id: TokenId) -> Option<AccountId> {
self.token_owner.get(&id)
}
#[ink(message)]
pub fn get_approved(&self, id: TokenId) -> Option<AccountId> {
self.token_approvals.get(&id)
}
#[ink(message)]
pub fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool {
self.approved_for_all(owner, operator)
}
#[ink(message)]
pub fn set_approval_for_all(
&mut self,
to: AccountId,
approved: bool,
) -> Result<(), Error> {
self.approve_for_all(to, approved)?;
Ok(())
}
#[ink(message)]
pub fn approve(&mut self, to: AccountId, id: TokenId) -> Result<(), Error> {
self.approve_for(&to, id)?;
Ok(())
}
#[ink(message)]
pub fn transfer(
&mut self,
destination: AccountId,
id: TokenId,
) -> Result<(), Error> {
let caller = self.env().caller();
self.transfer_token_from(&caller, &destination, id)?;
Ok(())
}
#[ink(message)]
pub fn transfer_from(
&mut self,
from: AccountId,
to: AccountId,
id: TokenId,
) -> Result<(), Error> {
self.transfer_token_from(&from, &to, id)?;
Ok(())
}
#[ink(message)]
pub 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(())
}
#[ink(message)]
pub fn burn(&mut self, id: TokenId) -> Result<(), Error> {
let caller = self.env().caller();
let Self {
token_owner,
owned_tokens_count,
..
} = self;
let owner = token_owner.get(&id).ok_or(Error::TokenNotFound)?;
if owner != caller {
return Err(Error::NotOwner)
};
let count = owned_tokens_count
.get(&caller)
.map(|c| c - 1)
.ok_or(Error::CannotFetchValue)?;
owned_tokens_count.insert(&caller, &count);
token_owner.remove(&id);
self.env().emit_event(Transfer {
from: Some(caller),
to: Some(AccountId::from([0x0; 32])),
id,
});
Ok(())
}
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(())
}
fn remove_token_from(
&mut self,
from: &AccountId,
id: TokenId,
) -> Result<(), Error> {
let Self {
token_owner,
owned_tokens_count,
..
} = self;
if token_owner.get(&id).is_none() {
return Err(Error::TokenNotFound)
}
let count = owned_tokens_count
.get(&from)
.map(|c| c - 1)
.ok_or(Error::CannotFetchValue)?;
owned_tokens_count.insert(&from, &count);
token_owner.remove(&id);
Ok(())
}
fn add_token_to(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> {
let Self {
token_owner,
owned_tokens_count,
..
} = self;
if token_owner.get(&id).is_some() {
return Err(Error::TokenExists)
}
if *to == AccountId::from([0x0; 32]) {
return Err(Error::NotAllowed)
};
let count = owned_tokens_count.get(to).map(|c| c + 1).unwrap_or(1);
owned_tokens_count.insert(to, &count);
token_owner.insert(&id, to);
Ok(())
}
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 approved {
self.operator_approvals.insert((&caller, &to), &());
} else {
self.operator_approvals.remove((&caller, &to));
}
Ok(())
}
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.get(&id).is_some() {
return Err(Error::CannotInsert)
} else {
self.token_approvals.insert(&id, to);
}
self.env().emit_event(Approval {
from: caller,
to: *to,
id,
});
Ok(())
}
fn clear_approval(&mut self, id: TokenId) {
self.token_approvals.remove(&id);
}
fn balance_of_or_zero(&self, of: &AccountId) -> u32 {
self.owned_tokens_count.get(of).unwrap_or(0)
}
fn approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool {
self.operator_approvals.get((&owner, &operator)).is_some()
}
fn approved_or_owner(&self, from: Option<AccountId>, id: TokenId) -> bool {
let owner = self.owner_of(id);
from != Some(AccountId::from([0x0; 32]))
&& (from == owner
|| from == self.token_approvals.get(&id)
|| self.approved_for_all(
owner.expect("Error with AccountId"),
from.expect("Error with AccountId"),
))
}
fn exists(&self, id: TokenId) -> bool {
self.token_owner.get(&id).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ink_lang as ink;
#[ink::test]
fn mint_works() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.owner_of(1), None);
assert_eq!(erc721.balance_of(accounts.alice), 0);
assert_eq!(erc721.mint(1), Ok(()));
assert_eq!(erc721.balance_of(accounts.alice), 1);
}
#[ink::test]
fn mint_existing_should_fail() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.mint(1), Ok(()));
assert_eq!(1, ink_env::test::recorded_events().count());
assert_eq!(erc721.balance_of(accounts.alice), 1);
assert_eq!(erc721.owner_of(1), Some(accounts.alice));
assert_eq!(erc721.mint(1), Err(Error::TokenExists));
}
#[ink::test]
fn transfer_works() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.mint(1), Ok(()));
assert_eq!(erc721.balance_of(accounts.alice), 1);
assert_eq!(erc721.balance_of(accounts.bob), 0);
assert_eq!(1, ink_env::test::recorded_events().count());
assert_eq!(erc721.transfer(accounts.bob, 1), Ok(()));
assert_eq!(2, ink_env::test::recorded_events().count());
assert_eq!(erc721.balance_of(accounts.bob), 1);
}
#[ink::test]
fn invalid_transfer_should_fail() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.transfer(accounts.bob, 2), Err(Error::TokenNotFound));
assert_eq!(erc721.owner_of(2), None);
assert_eq!(erc721.mint(2), Ok(()));
assert_eq!(erc721.balance_of(accounts.alice), 1);
assert_eq!(erc721.owner_of(2), Some(accounts.alice));
set_caller(accounts.bob);
assert_eq!(erc721.transfer(accounts.eve, 2), Err(Error::NotApproved));
}
#[ink::test]
fn approved_transfer_works() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.mint(1), Ok(()));
assert_eq!(erc721.owner_of(1), Some(accounts.alice));
assert_eq!(erc721.approve(accounts.bob, 1), Ok(()));
set_caller(accounts.bob);
assert_eq!(
erc721.transfer_from(accounts.alice, accounts.eve, 1),
Ok(())
);
assert_eq!(erc721.owner_of(1), Some(accounts.eve));
assert_eq!(erc721.balance_of(accounts.alice), 0);
assert_eq!(erc721.balance_of(accounts.bob), 0);
assert_eq!(erc721.balance_of(accounts.eve), 1);
}
#[ink::test]
fn approved_for_all_works() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.mint(1), Ok(()));
assert_eq!(erc721.mint(2), Ok(()));
assert_eq!(erc721.balance_of(accounts.alice), 2);
assert_eq!(erc721.set_approval_for_all(accounts.bob, true), Ok(()));
assert!(erc721.is_approved_for_all(accounts.alice, accounts.bob));
set_caller(accounts.bob);
assert_eq!(
erc721.transfer_from(accounts.alice, accounts.eve, 1),
Ok(())
);
assert_eq!(erc721.owner_of(1), Some(accounts.eve));
assert_eq!(erc721.balance_of(accounts.alice), 1);
assert_eq!(
erc721.transfer_from(accounts.alice, accounts.eve, 2),
Ok(())
);
assert_eq!(erc721.balance_of(accounts.bob), 0);
assert_eq!(erc721.balance_of(accounts.eve), 2);
set_caller(accounts.alice);
assert_eq!(erc721.set_approval_for_all(accounts.bob, false), Ok(()));
assert!(!erc721.is_approved_for_all(accounts.alice, accounts.bob));
}
#[ink::test]
fn not_approved_transfer_should_fail() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.mint(1), Ok(()));
assert_eq!(erc721.balance_of(accounts.alice), 1);
assert_eq!(erc721.balance_of(accounts.bob), 0);
assert_eq!(erc721.balance_of(accounts.eve), 0);
set_caller(accounts.eve);
assert_eq!(
erc721.transfer_from(accounts.alice, accounts.frank, 1),
Err(Error::NotApproved)
);
assert_eq!(erc721.balance_of(accounts.alice), 1);
assert_eq!(erc721.balance_of(accounts.bob), 0);
assert_eq!(erc721.balance_of(accounts.eve), 0);
}
#[ink::test]
fn burn_works() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.mint(1), Ok(()));
assert_eq!(erc721.balance_of(accounts.alice), 1);
assert_eq!(erc721.owner_of(1), Some(accounts.alice));
assert_eq!(erc721.burn(1), Ok(()));
assert_eq!(erc721.balance_of(accounts.alice), 0);
assert_eq!(erc721.owner_of(1), None);
}
#[ink::test]
fn burn_fails_token_not_found() {
let mut erc721 = Erc721::new();
assert_eq!(erc721.burn(1), Err(Error::TokenNotFound));
}
#[ink::test]
fn burn_fails_not_owner() {
let accounts =
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
let mut erc721 = Erc721::new();
assert_eq!(erc721.mint(1), Ok(()));
set_caller(accounts.eve);
assert_eq!(erc721.burn(1), Err(Error::NotOwner));
}
fn set_caller(sender: AccountId) {
ink_env::test::set_caller::<ink_env::DefaultEnvironment>(sender);
}
}
}