#![allow(unused_variables, missing_docs)]
use alloc::collections::BTreeMap;
use odra::{
casper_types::{bytesrepr::Bytes, U256},
named_keys::{
base64_encoded_key_value_storage, compound_key_value_storage, single_value_storage
},
prelude::*,
ContractRef
};
pub trait CEP95Interface {
fn name(&self) -> String;
fn symbol(&self) -> String;
fn balance_of(&self, owner: Address) -> U256;
fn owner_of(&self, token_id: U256) -> Option<Address>;
fn safe_transfer_from(
&mut self,
from: Address,
to: Address,
token_id: U256,
data: Option<Bytes>
);
fn transfer_from(&mut self, from: Address, to: Address, token_id: U256);
fn approve(&mut self, spender: Address, token_id: U256);
fn revoke_approval(&mut self, token_id: U256);
fn approved_for(&self, token_id: U256) -> Option<Address>;
fn approve_for_all(&mut self, operator: Address);
fn revoke_approval_for_all(&mut self, operator: Address);
fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool;
fn token_metadata(&self, token_id: U256) -> Vec<(String, String)>;
}
#[odra::module]
struct CEP95Receiver;
#[odra::module]
impl CEP95Receiver {
#[allow(dead_code)]
pub fn on_cep95_received(
&mut self,
operator: &Address,
from: &Address,
token_id: &U256,
data: &Option<Bytes>
) -> bool {
true
}
}
const KEY_BALANCES: &str = "balances";
const KEY_NAME: &str = "name";
const KEY_SYMBOL: &str = "symbol";
const KEY_APPROVED: &str = "approvals";
const KEY_OPERATORS: &str = "operators";
const KEY_METADATA: &str = "token_metadata";
const KEY_OWNERS: &str = "owners";
single_value_storage!(Cep95Name, String, KEY_NAME, Error::ValueNotSet);
single_value_storage!(Cep95Symbol, String, KEY_SYMBOL, Error::ValueNotSet);
base64_encoded_key_value_storage!(Cep95Balances, KEY_BALANCES, Address, U256);
base64_encoded_key_value_storage!(Cep95Approvals, KEY_APPROVED, U256, Option<Address>);
compound_key_value_storage!(Cep95Operators, KEY_OPERATORS, Address, bool);
base64_encoded_key_value_storage!(Cep95Owners, KEY_OWNERS, U256, Option<Address>);
base64_encoded_key_value_storage!(Cep95Metadata, KEY_METADATA, U256, BTreeMap<String, String>);
#[odra::odra_error]
pub enum Error {
ValueNotSet = 40_000,
TransferFailed = 40_001,
NotAnOwnerOrApproved = 40_002,
ApprovalToCurrentOwner = 40_003,
ApproveToCaller = 40_004,
InvalidTokenId = 40_005,
TokenAlreadyExists = 40_006
}
#[odra::event]
pub struct Mint {
pub to: Address,
pub token_id: U256
}
#[odra::event]
pub struct Burn {
pub from: Address,
pub token_id: U256
}
#[odra::event]
pub struct Transfer {
pub from: Address,
pub to: Address,
pub token_id: U256
}
#[odra::event]
pub struct Approval {
pub owner: Address,
pub spender: Address,
pub token_id: U256
}
#[odra::event]
pub struct RevokeApproval {
pub owner: Address,
pub spender: Address,
pub token_id: U256
}
#[odra::event]
pub struct ApprovalForAll {
pub owner: Address,
pub operator: Address
}
#[odra::event]
pub struct RevokeApprovalForAll {
pub owner: Address,
pub operator: Address
}
#[odra::event]
pub struct MetadataUpdate {
pub token_id: U256
}
#[odra::module(
events = [
Transfer,
Approval,
RevokeApproval,
ApprovalForAll,
RevokeApprovalForAll,
Mint,
Burn,
MetadataUpdate
],
errors = Error
)]
pub struct Cep95 {
pub name: SubModule<Cep95Name>,
pub symbol: SubModule<Cep95Symbol>,
pub balances: SubModule<Cep95Balances>,
pub owners: SubModule<Cep95Owners>,
pub approvals: SubModule<Cep95Approvals>,
pub operators: SubModule<Cep95Operators>,
pub metadata: SubModule<Cep95Metadata>
}
#[odra::module]
impl CEP95Interface for Cep95 {
fn name(&self) -> String {
self.name.get()
}
fn symbol(&self) -> String {
self.symbol.get()
}
fn balance_of(&self, owner: Address) -> U256 {
self.balances.get(&owner).unwrap_or_default()
}
fn owner_of(&self, token_id: U256) -> Option<Address> {
self.owners.get(&token_id).flatten()
}
fn safe_transfer_from(
&mut self,
from: Address,
to: Address,
token_id: U256,
data: Option<Bytes>
) {
self.transfer_from(from, to, token_id);
if to.is_contract() {
let mut receiver = CEP95ReceiverContractRef::new(self.env(), to);
let caller = self.env().caller();
let result = receiver.on_cep95_received(&caller, &from, &token_id, &data);
if !result {
self.env().revert(Error::TransferFailed);
}
}
}
fn transfer_from(&mut self, from: Address, to: Address, token_id: U256) {
self.assert_exists(&token_id);
let caller = self.env().caller();
let owner = self
.owner_of(token_id)
.unwrap_or_revert_with(self, Error::InvalidTokenId);
if owner != from {
self.env().revert(Error::NotAnOwnerOrApproved);
}
let is_authorized = owner == caller
|| self.is_approved_for_all(from, caller)
|| self.is_spender(token_id, caller);
if !is_authorized {
self.env().revert(Error::NotAnOwnerOrApproved);
}
self.raw_transfer(to, token_id); }
fn approve(&mut self, spender: Address, token_id: U256) {
let caller = self.env().caller();
let owner = self
.owner_of(token_id)
.unwrap_or_revert_with(self, Error::InvalidTokenId);
self.set_approve(token_id, owner, Some(spender));
self.env().emit_event(Approval {
owner,
spender,
token_id
});
}
fn revoke_approval(&mut self, token_id: U256) {
let spender = self
.approved_for(token_id)
.unwrap_or_revert_with(self, Error::ValueNotSet);
let owner = self
.owner_of(token_id)
.unwrap_or_revert_with(self, Error::InvalidTokenId);
self.set_approve(token_id, owner, None);
self.env().emit_event(RevokeApproval {
owner,
spender,
token_id
});
}
fn approved_for(&self, token_id: U256) -> Option<Address> {
self.assert_exists(&token_id);
self.approvals.get(&token_id).flatten()
}
fn approve_for_all(&mut self, operator: Address) {
let caller = self.env().caller();
self.set_approval_for_all(caller, operator, true);
self.env().emit_event(ApprovalForAll {
owner: caller,
operator
});
}
fn revoke_approval_for_all(&mut self, operator: Address) {
let caller = self.env().caller();
self.set_approval_for_all(caller, operator, false);
self.env().emit_event(RevokeApprovalForAll {
owner: caller,
operator
});
}
fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool {
self.operators.get_or_default(&owner, &operator)
}
fn token_metadata(&self, token_id: U256) -> Vec<(String, String)> {
self.assert_exists(&token_id);
self.metadata
.get(&token_id)
.unwrap_or_default()
.into_iter()
.collect()
}
}
impl Cep95 {
pub fn init(&mut self, name: String, symbol: String) {
self.name.set(name);
self.symbol.set(symbol);
self.balances.init();
self.owners.init();
self.approvals.init();
self.operators.init();
self.metadata.init();
}
#[inline]
pub fn assert_exists(&self, token_id: &U256) {
if !self.exists(token_id) {
self.env().revert(Error::InvalidTokenId);
}
}
#[inline]
pub fn exists(&self, token_id: &U256) -> bool {
self.owners.get(token_id).flatten().is_some()
}
#[inline]
pub fn clear_approval(&mut self, token_id: &U256) {
if self.approvals.get(token_id).is_some() {
self.approvals.set(token_id, None);
}
}
pub fn raw_mint(&mut self, to: Address, token_id: U256, metadata: Vec<(String, String)>) {
if self.exists(&token_id) {
self.env().revert(Error::TokenAlreadyExists);
}
self.balances.set(&to, self.balance_of(to) + 1);
self.owners.set(&token_id, Some(to));
self.metadata.set(&token_id, BTreeMap::from_iter(metadata));
self.env().emit_event(Mint { to, token_id });
}
pub fn raw_burn(&mut self, token_id: U256) {
self.assert_exists(&token_id);
let owner = self
.owner_of(token_id)
.unwrap_or_revert_with(self, Error::ValueNotSet);
self.clear_approval(&token_id);
self.balances.set(&owner, self.balance_of(owner) - 1);
self.owners.set(&token_id, None);
self.metadata.set(&token_id, Default::default());
self.env().emit_event(Burn {
from: owner,
token_id
});
}
pub fn set_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
self.raw_update_metadata(token_id, metadata, BTreeMap::new());
}
pub fn update_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
let current_metadata = self.metadata.get(&token_id).unwrap_or_default();
self.raw_update_metadata(token_id, metadata, current_metadata);
}
pub fn raw_transfer(&mut self, to: Address, token_id: U256) -> Address {
self.clear_approval(&token_id);
let from = self
.owner_of(token_id)
.unwrap_or_revert_with(self, Error::InvalidTokenId);
self.balances.set(&from, self.balance_of(from) - 1);
self.balances.set(&to, self.balance_of(to) + 1);
self.owners.set(&token_id, Some(to));
self.env().emit_event(Transfer { from, to, token_id });
from
}
fn raw_update_metadata(
&mut self,
token_id: U256,
new_metadata: Vec<(String, String)>,
mut current_metadata: BTreeMap<String, String>
) {
self.assert_exists(&token_id);
for (k, v) in new_metadata {
current_metadata.insert(k, v);
}
self.metadata.set(&token_id, current_metadata);
self.env().emit_event(MetadataUpdate { token_id });
}
#[inline]
fn set_approve(&mut self, token_id: U256, owner: Address, spender: Option<Address>) {
if Some(owner) == spender {
self.env().revert(Error::ApprovalToCurrentOwner);
}
let caller = self.env().caller();
if caller != owner && !self.is_approved_for_all(owner, caller) {
self.env().revert(Error::NotAnOwnerOrApproved);
}
self.approvals.set(&token_id, spender);
}
#[inline]
fn set_approval_for_all(&mut self, caller: Address, operator: Address, approved: bool) {
if caller == operator {
self.env().revert(Error::ApproveToCaller)
}
self.operators.set(&caller, &operator, approved);
}
#[inline]
fn is_spender(&self, token_id: U256, spender: Address) -> bool {
self.approved_for(token_id) == Some(spender)
}
}
mod utils {
#![allow(dead_code)]
use crate::access::Ownable;
use super::*;
#[odra::module]
pub(crate) struct BasicCep95 {
token: SubModule<Cep95>,
ownable: SubModule<Ownable>
}
#[odra::module]
impl BasicCep95 {
pub fn init(&mut self, name: String, symbol: String) {
let owner = self.env().caller();
self.ownable.init(owner);
self.token.init(name, symbol);
}
delegate! {
to self.token {
fn name(&self) -> String;
fn symbol(&self) -> String;
fn balance_of(&self, owner: Address) -> U256;
fn owner_of(&self, token_id: U256) -> Option<Address>;
fn safe_transfer_from(&mut self, from: Address, to: Address, token_id: U256, data: Option<Bytes>);
fn transfer_from(&mut self, from: Address, to: Address, token_id: U256);
fn approve(&mut self, spender: Address, token_id: U256);
fn revoke_approval(&mut self, token_id: U256);
fn approved_for(&self, token_id: U256) -> Option<Address>;
fn approve_for_all(&mut self, operator: Address);
fn revoke_approval_for_all(&mut self, operator: Address);
fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool;
fn token_metadata(&self, token_id: U256) -> Vec<(String, String)>;
}
}
pub fn mint(&mut self, to: Address, token_id: U256, metadata: Vec<(String, String)>) {
self.assert_owner();
self.token.raw_mint(to, token_id, metadata);
}
pub fn burn(&mut self, token_id: U256) {
self.assert_owner();
self.token.raw_burn(token_id);
}
pub fn set_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
self.assert_owner();
self.token.set_metadata(token_id, metadata);
}
pub fn update_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
self.assert_owner();
self.token.update_metadata(token_id, metadata);
}
pub fn assert_owner(&self) {
self.ownable.assert_owner(&self.env().caller());
}
}
#[odra::module]
pub(crate) struct NFTReceiver {
#[allow(clippy::type_complexity)]
last_call_data: Var<((Address, Address), (U256, Option<Bytes>))>
}
#[odra::module]
impl NFTReceiver {
#[allow(dead_code)]
pub fn on_cep95_received(
&mut self,
operator: Address,
from: Address,
token_id: U256,
data: Option<Bytes>
) -> bool {
self.last_call_data
.set(((operator, from), (token_id, data.clone())));
true
}
#[allow(dead_code)]
pub fn last_call_result(&self) -> ((Address, Address), (U256, Option<Bytes>)) {
self.last_call_data.get().unwrap_or_revert(self)
}
}
#[odra::module]
pub(crate) struct RejectingNFTReceiver;
#[odra::module]
impl RejectingNFTReceiver {
#[allow(dead_code)]
pub fn on_cep95_received(
&mut self,
operator: Address,
from: Address,
token_id: U256,
data: Option<Bytes>
) -> bool {
false
}
}
#[odra::module]
pub(crate) struct BasicContract;
#[odra::module]
impl BasicContract {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cep95::utils::*;
use odra::{
host::{Deployer, HostEnv, NoArgs},
prelude::Addressable,
VmError
};
use odra_test;
fn setup() -> (HostEnv, BasicCep95HostRef) {
let env = odra_test::env();
let cep95 = BasicCep95::try_deploy(
&env,
BasicCep95InitArgs {
name: "TestToken".to_string(),
symbol: "TT".to_string()
}
)
.unwrap();
(env, cep95)
}
#[test]
fn test_deploy() {
let env = odra_test::env();
let cep95 = BasicCep95::try_deploy(
&env,
BasicCep95InitArgs {
name: "TestToken".to_string(),
symbol: "TT".to_string()
}
);
assert!(cep95.is_ok());
}
#[test]
fn test_name() {
let (env, cep95) = setup();
let name = cep95.name();
assert_eq!(name, "TestToken");
}
#[test]
fn test_symbol() {
let (env, cep95) = setup();
let symbol = cep95.symbol();
assert_eq!(symbol, "TT");
}
#[test]
fn test_mint() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
assert_eq!(cep95.balance_of(owner), U256::from(1));
assert_eq!(cep95.owner_of(token_id), Some(owner));
assert_eq!(
cep95.token_metadata(token_id),
vec![("key".to_string(), "value".to_string())]
);
assert!(env.emitted(&cep95, "Mint"));
}
#[test]
fn test_minting_existing_token() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata.clone());
let result = cep95.try_mint(owner, token_id, metadata);
assert_eq!(result, Err(Error::TokenAlreadyExists.into()));
}
#[test]
fn test_mint_many_tokens() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id1 = U256::from(1);
let metadata1 = vec![("key1".to_string(), "value1".to_string())];
cep95.mint(owner, token_id1, metadata1);
let token_id2 = U256::from(2);
let metadata2 = vec![("key2".to_string(), "value2".to_string())];
cep95.mint(owner, token_id2, metadata2);
assert_eq!(cep95.balance_of(owner), U256::from(2));
assert_eq!(cep95.owner_of(token_id1), Some(owner));
assert_eq!(cep95.owner_of(token_id2), Some(owner));
}
#[test]
fn test_burn() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
cep95.burn(token_id);
assert_eq!(cep95.balance_of(owner), U256::from(0));
assert_eq!(cep95.owner_of(token_id), None);
assert_eq!(
cep95.try_token_metadata(token_id),
Err(Error::InvalidTokenId.into())
);
assert!(env.emitted(&cep95, "Burn"));
}
#[test]
fn test_burn_non_existing_token() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let result = cep95.try_burn(token_id);
assert_eq!(result, Err(Error::InvalidTokenId.into()));
}
#[test]
fn test_safe_transfer_to_receiver() {
let (env, mut cep95) = setup();
let nft_receiver = NFTReceiver::deploy(&env, NoArgs);
let recipient = nft_receiver.address();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
cep95.safe_transfer_from(owner, recipient, token_id, None);
assert_eq!(cep95.balance_of(owner), U256::from(0));
assert_eq!(cep95.balance_of(recipient), U256::from(1));
assert_eq!(
nft_receiver.last_call_result(),
((owner, owner), (token_id, None))
);
}
#[test]
fn test_safe_transfer_to_non_receiver() {
let (env, mut cep95) = setup();
let contract = BasicContract::deploy(&env, NoArgs);
let recipient = contract.address();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let result = cep95.try_safe_transfer_from(owner, recipient, token_id, None);
assert_eq!(
result,
Err(OdraError::VmError(VmError::NoSuchMethod(
"on_cep95_received".to_string()
)))
);
assert_eq!(cep95.balance_of(owner), U256::from(1));
assert_eq!(cep95.balance_of(recipient), U256::from(0));
}
#[test]
fn test_safe_transfer_to_rejecting_receiver() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let contract = RejectingNFTReceiver::deploy(&env, NoArgs);
let recipient = contract.address();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let result = cep95.try_safe_transfer_from(owner, recipient, token_id, None);
assert_eq!(result, Err(Error::TransferFailed.into()));
assert_eq!(cep95.balance_of(owner), U256::from(1));
assert_eq!(cep95.balance_of(recipient), U256::from(0));
}
#[test]
fn test_transfer() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let recipient = env.get_account(10);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
cep95.transfer_from(owner, recipient, token_id);
assert_eq!(cep95.balance_of(owner), U256::from(0));
assert_eq!(cep95.balance_of(recipient), U256::from(1));
assert!(env.emitted(&cep95, "Transfer"));
}
#[test]
fn test_transfer_non_existing_token() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let recipient = env.get_account(10);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let result = cep95.try_transfer_from(owner, recipient, U256::from(2));
assert_eq!(result, Err(Error::InvalidTokenId.into()));
}
#[test]
fn test_transfer_from_non_owner() {
let (env, mut cep95) = setup();
let recipient = env.get_account(10);
let non_owner = env.get_account(11);
let owner = env.get_account(0);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let result = cep95.try_transfer_from(non_owner, recipient, token_id);
assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
assert_eq!(cep95.balance_of(owner), U256::from(1));
assert_eq!(cep95.balance_of(recipient), U256::from(0));
}
#[test]
fn test_approve() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let spender = env.get_account(10);
cep95.approve(spender, token_id);
assert_eq!(cep95.approved_for(token_id), Some(spender));
assert!(env.emitted_event(
&cep95,
Approval {
owner,
spender,
token_id
}
));
}
#[test]
fn test_approve_by_non_owner() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let non_owner = env.get_account(11);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let spender = env.get_account(10);
env.set_caller(non_owner);
let result = cep95.try_approve(spender, token_id);
assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
}
#[test]
fn test_approve_by_spender() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let spender = env.get_account(10);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.approve_for_all(spender);
cep95.mint(owner, token_id, metadata);
env.set_caller(spender);
let result = cep95.try_approve(spender, token_id);
assert!(result.is_ok());
assert!(env.emitted_event(
&cep95,
Approval {
owner,
spender,
token_id
}
));
}
#[test]
fn test_transfer_by_approved() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let recipient = env.get_account(10);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let spender = env.get_account(11);
cep95.approve(spender, token_id);
env.set_caller(spender);
cep95.transfer_from(owner, recipient, token_id);
assert_eq!(cep95.balance_of(owner), U256::from(0));
assert_eq!(cep95.balance_of(recipient), U256::from(1));
assert!(env.emitted_event(
&cep95,
Transfer {
from: owner,
to: recipient,
token_id
}
));
}
#[test]
fn test_approve_for_all() {
let (env, mut cep95) = setup();
let owner = env.caller();
let operator = env.get_account(10);
cep95.approve_for_all(operator);
assert!(cep95.is_approved_for_all(owner, operator));
assert!(env.emitted(&cep95, "ApprovalForAll"));
}
#[test]
fn test_revoke_approval_for_all() {
let (env, mut cep95) = setup();
let owner = env.caller();
let operator = env.get_account(10);
cep95.approve_for_all(operator);
cep95.revoke_approval_for_all(operator);
assert!(!cep95.is_approved_for_all(owner, operator));
assert!(env.emitted(&cep95, "RevokeApprovalForAll"));
}
#[test]
fn test_approve_for_all_self() {
let (env, mut cep95) = setup();
let owner = env.caller();
let result = cep95.try_approve_for_all(owner);
assert_eq!(result, Err(Error::ApproveToCaller.into()));
}
#[test]
fn test_revoke_approval() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let spender = env.get_account(10);
cep95.approve(spender, token_id);
cep95.revoke_approval(token_id);
assert_eq!(cep95.approved_for(token_id), None);
assert!(env.emitted(&cep95, "RevokeApproval"));
}
#[test]
fn test_revoke_approval_by_spender() {
let (env, mut cep95) = setup();
let owner = env.caller();
let spender = env.get_account(10);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.approve_for_all(spender);
cep95.mint(owner, token_id, metadata);
cep95.approve(spender, token_id);
env.set_caller(spender);
cep95.revoke_approval(token_id);
assert_eq!(cep95.approved_for(token_id), None);
assert!(env.emitted_event(
&cep95,
RevokeApproval {
owner,
spender,
token_id
}
));
}
#[test]
fn revoke_non_existing_approval() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let result = cep95.try_revoke_approval(token_id);
assert_eq!(result, Err(Error::ValueNotSet.into()));
}
#[test]
fn test_revoke_approval_by_non_owner() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let non_owner = env.get_account(11);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
let spender = env.get_account(10);
cep95.approve(spender, token_id);
env.set_caller(non_owner);
let result = cep95.try_revoke_approval(token_id);
assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
}
#[test]
fn test_approval_cleared_after_transfer() {
let (env, mut cep95) = setup();
let owner = env.get_account(0);
let spender = env.get_account(11);
let recipient = env.get_account(10);
let attacker_dest = env.get_account(12);
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
cep95.approve(spender, token_id);
cep95.transfer_from(owner, recipient, token_id);
assert_eq!(cep95.approved_for(token_id), None);
env.set_caller(spender);
let result = cep95.try_transfer_from(recipient, attacker_dest, token_id);
assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
assert_eq!(cep95.owner_of(token_id), Some(recipient));
assert_eq!(cep95.balance_of(recipient), U256::from(1));
assert_eq!(cep95.balance_of(attacker_dest), U256::from(0));
}
#[test]
fn test_metadata() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id, metadata);
assert_eq!(
cep95.token_metadata(token_id),
vec![("key".to_string(), "value".to_string())]
);
}
#[test]
fn test_transfer_by_operator() {
let (env, mut cep95) = setup();
let owner = env.caller();
let recipient = env.get_account(10);
let operator = env.get_account(11);
let token_id1 = U256::from(1);
let metadata = vec![("key".to_string(), "value".to_string())];
cep95.mint(owner, token_id1, metadata.clone());
cep95.approve_for_all(operator);
let token_id2 = U256::from(2);
cep95.mint(owner, token_id2, metadata);
env.set_caller(operator);
cep95.transfer_from(owner, recipient, token_id1);
cep95.transfer_from(owner, recipient, token_id2);
assert_eq!(cep95.balance_of(owner), U256::from(0));
assert_eq!(cep95.balance_of(recipient), U256::from(2));
}
#[test]
fn test_update_metadata() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![
("age".to_string(), "30".to_string()),
("name".to_string(), "Alice".to_string()),
];
cep95.mint(owner, token_id, metadata);
let new_metadata = vec![("name".to_string(), "Bob".to_string())];
cep95.update_metadata(token_id, new_metadata);
assert_eq!(
cep95.token_metadata(token_id),
vec![
("age".to_string(), "30".to_string()),
("name".to_string(), "Bob".to_string()),
]
);
assert!(env.emitted(&cep95, "MetadataUpdate"));
}
#[test]
fn test_set_metadata() {
let (env, mut cep95) = setup();
let owner = env.caller();
let token_id = U256::from(1);
let metadata = vec![
("age".to_string(), "30".to_string()),
("name".to_string(), "Alice".to_string()),
];
cep95.mint(owner, token_id, metadata);
let new_metadata = vec![("name".to_string(), "Bob".to_string())];
cep95.set_metadata(token_id, new_metadata);
assert_eq!(
cep95.token_metadata(token_id),
vec![("name".to_string(), "Bob".to_string())]
);
assert!(env.emitted(&cep95, "MetadataUpdate"));
}
}