#![cfg_attr(not(feature = "std"), no_std)]
use ink_env::AccountId;
use ink_lang as ink;
use ink_prelude::vec::Vec;
#[cfg_attr(test, allow(dead_code))]
const ON_ERC_1155_RECEIVED_SELECTOR: [u8; 4] = [0xF2, 0x3A, 0x6E, 0x61];
const _ON_ERC_1155_BATCH_RECEIVED_SELECTOR: [u8; 4] = [0xBC, 0x19, 0x7C, 0x81];
pub type TokenId = u128;
type Balance = <ink_env::DefaultEnvironment as ink_env::Environment>::Balance;
#[derive(Debug, PartialEq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
UnexistentToken,
ZeroAddressTransfer,
NotApproved,
InsufficientBalance,
SelfApproval,
BatchTransferMismatch,
}
pub type Result<T> = core::result::Result<T, Error>;
macro_rules! ensure {
( $condition:expr, $error:expr $(,)? ) => {{
if !$condition {
return ::core::result::Result::Err(::core::convert::Into::into($error))
}
}};
}
#[ink::trait_definition]
pub trait Erc1155 {
#[ink(message)]
fn safe_transfer_from(
&mut self,
from: AccountId,
to: AccountId,
token_id: TokenId,
value: Balance,
data: Vec<u8>,
) -> Result<()>;
#[ink(message)]
fn safe_batch_transfer_from(
&mut self,
from: AccountId,
to: AccountId,
token_ids: Vec<TokenId>,
values: Vec<Balance>,
data: Vec<u8>,
) -> Result<()>;
#[ink(message)]
fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance;
#[ink(message)]
fn balance_of_batch(
&self,
owners: Vec<AccountId>,
token_ids: Vec<TokenId>,
) -> Vec<Balance>;
#[ink(message)]
fn set_approval_for_all(&mut self, operator: AccountId, approved: bool)
-> Result<()>;
#[ink(message)]
fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool;
}
#[ink::trait_definition]
pub trait Erc1155TokenReceiver {
#[ink(message, selector = 0xF23A6E61)]
fn on_received(
&mut self,
operator: AccountId,
from: AccountId,
token_id: TokenId,
value: Balance,
data: Vec<u8>,
) -> Vec<u8>;
#[ink(message, selector = 0xBC197C81)]
fn on_batch_received(
&mut self,
operator: AccountId,
from: AccountId,
token_ids: Vec<TokenId>,
values: Vec<Balance>,
data: Vec<u8>,
) -> Vec<u8>;
}
#[ink::contract]
mod erc1155 {
use super::*;
use ink_storage::{
traits::SpreadAllocate,
Mapping,
};
type Owner = AccountId;
type Operator = AccountId;
#[ink(event)]
pub struct TransferSingle {
#[ink(topic)]
operator: Option<AccountId>,
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
token_id: TokenId,
value: Balance,
}
#[ink(event)]
pub struct ApprovalForAll {
#[ink(topic)]
owner: AccountId,
#[ink(topic)]
operator: AccountId,
approved: bool,
}
#[ink(event)]
pub struct Uri {
value: ink_prelude::string::String,
#[ink(topic)]
token_id: TokenId,
}
#[ink(storage)]
#[derive(Default, SpreadAllocate)]
pub struct Contract {
balances: Mapping<(AccountId, TokenId), Balance>,
approvals: Mapping<(Owner, Operator), ()>,
token_id_nonce: TokenId,
}
impl Contract {
#[ink(constructor)]
pub fn new() -> Self {
ink_lang::utils::initialize_contract(|_| {})
}
#[ink(message)]
pub fn create(&mut self, value: Balance) -> TokenId {
let caller = self.env().caller();
self.token_id_nonce += 1;
self.balances.insert(&(caller, self.token_id_nonce), &value);
self.env().emit_event(TransferSingle {
operator: Some(caller),
from: None,
to: if value == 0 { None } else { Some(caller) },
token_id: self.token_id_nonce,
value,
});
self.token_id_nonce
}
#[ink(message)]
pub fn mint(&mut self, token_id: TokenId, value: Balance) -> Result<()> {
ensure!(token_id <= self.token_id_nonce, Error::UnexistentToken);
let caller = self.env().caller();
self.balances.insert(&(caller, token_id), &value);
self.env().emit_event(TransferSingle {
operator: Some(caller),
from: None,
to: Some(caller),
token_id,
value,
});
Ok(())
}
fn perform_transfer(
&mut self,
from: AccountId,
to: AccountId,
token_id: TokenId,
value: Balance,
) {
let mut sender_balance = self
.balances
.get(&(from, token_id))
.expect("Caller should have ensured that `from` holds `token_id`.");
sender_balance -= value;
self.balances.insert(&(from, token_id), &sender_balance);
let mut recipient_balance = self.balances.get(&(to, token_id)).unwrap_or(0);
recipient_balance += value;
self.balances.insert(&(to, token_id), &recipient_balance);
let caller = self.env().caller();
self.env().emit_event(TransferSingle {
operator: Some(caller),
from: Some(from),
to: Some(from),
token_id,
value,
});
}
#[cfg_attr(test, allow(unused_variables))]
fn transfer_acceptance_check(
&mut self,
caller: AccountId,
from: AccountId,
to: AccountId,
token_id: TokenId,
value: Balance,
data: Vec<u8>,
) {
#[cfg(not(test))]
{
use ink_env::call::{
build_call,
Call,
ExecutionInput,
Selector,
};
let params = build_call::<Environment>()
.call_type(Call::new().callee(to).gas_limit(5000))
.exec_input(
ExecutionInput::new(Selector::new(ON_ERC_1155_RECEIVED_SELECTOR))
.push_arg(caller)
.push_arg(from)
.push_arg(token_id)
.push_arg(value)
.push_arg(data),
)
.returns::<Vec<u8>>()
.params();
match ink_env::invoke_contract(¶ms) {
Ok(v) => {
ink_env::debug_println!(
"Received return value \"{:?}\" from contract {:?}",
v,
from
);
assert_eq!(
v,
&ON_ERC_1155_RECEIVED_SELECTOR[..],
"The recipient contract at {:?} does not accept token transfers.\n
Expected: {:?}, Got {:?}", to, ON_ERC_1155_RECEIVED_SELECTOR, v
)
}
Err(e) => {
match e {
ink_env::Error::CodeNotFound
| ink_env::Error::NotCallable => {
ink_env::debug_println!("Recipient at {:?} from is not a smart contract ({:?})", from, e);
}
_ => {
panic!(
"Got error \"{:?}\" while trying to call {:?}",
e, from
)
}
}
}
}
}
}
}
impl super::Erc1155 for Contract {
#[ink(message)]
fn safe_transfer_from(
&mut self,
from: AccountId,
to: AccountId,
token_id: TokenId,
value: Balance,
data: Vec<u8>,
) -> Result<()> {
let caller = self.env().caller();
if caller != from {
ensure!(self.is_approved_for_all(from, caller), Error::NotApproved);
}
ensure!(to != AccountId::default(), Error::ZeroAddressTransfer);
let balance = self.balance_of(from, token_id);
ensure!(balance >= value, Error::InsufficientBalance);
self.perform_transfer(from, to, token_id, value);
self.transfer_acceptance_check(caller, from, to, token_id, value, data);
Ok(())
}
#[ink(message)]
fn safe_batch_transfer_from(
&mut self,
from: AccountId,
to: AccountId,
token_ids: Vec<TokenId>,
values: Vec<Balance>,
data: Vec<u8>,
) -> Result<()> {
let caller = self.env().caller();
if caller != from {
ensure!(self.is_approved_for_all(from, caller), Error::NotApproved);
}
ensure!(to != AccountId::default(), Error::ZeroAddressTransfer);
ensure!(!token_ids.is_empty(), Error::BatchTransferMismatch);
ensure!(
token_ids.len() == values.len(),
Error::BatchTransferMismatch,
);
let transfers = token_ids.iter().zip(values.iter());
for (&id, &v) in transfers.clone() {
let balance = self.balance_of(from, id);
ensure!(balance >= v, Error::InsufficientBalance);
}
for (&id, &v) in transfers {
self.perform_transfer(from, to, id, v);
}
self.transfer_acceptance_check(
caller,
from,
to,
token_ids[0],
values[0],
data,
);
Ok(())
}
#[ink(message)]
fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance {
self.balances.get(&(owner, token_id)).unwrap_or(0)
}
#[ink(message)]
fn balance_of_batch(
&self,
owners: Vec<AccountId>,
token_ids: Vec<TokenId>,
) -> Vec<Balance> {
let mut output = Vec::new();
for o in &owners {
for t in &token_ids {
let amount = self.balance_of(*o, *t);
output.push(amount);
}
}
output
}
#[ink(message)]
fn set_approval_for_all(
&mut self,
operator: AccountId,
approved: bool,
) -> Result<()> {
let caller = self.env().caller();
ensure!(operator != caller, Error::SelfApproval);
if approved {
self.approvals.insert((&caller, &operator), &());
} else {
self.approvals.remove((&caller, &operator));
}
self.env().emit_event(ApprovalForAll {
owner: caller,
operator,
approved,
});
Ok(())
}
#[ink(message)]
fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool {
self.approvals.get((&owner, &operator)).is_some()
}
}
impl super::Erc1155TokenReceiver for Contract {
#[ink(message, selector = 0xF23A6E61)]
fn on_received(
&mut self,
_operator: AccountId,
_from: AccountId,
_token_id: TokenId,
_value: Balance,
_data: Vec<u8>,
) -> Vec<u8> {
unimplemented!("This smart contract does not accept token transfer.")
}
#[ink(message, selector = 0xBC197C81)]
fn on_batch_received(
&mut self,
_operator: AccountId,
_from: AccountId,
_token_ids: Vec<TokenId>,
_values: Vec<Balance>,
_data: Vec<u8>,
) -> Vec<u8> {
unimplemented!("This smart contract does not accept batch token transfers.")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Erc1155;
use ink_lang as ink;
fn set_sender(sender: AccountId) {
ink_env::test::set_caller::<Environment>(sender);
}
fn default_accounts() -> ink_env::test::DefaultAccounts<Environment> {
ink_env::test::default_accounts::<Environment>()
}
fn alice() -> AccountId {
default_accounts().alice
}
fn bob() -> AccountId {
default_accounts().bob
}
fn charlie() -> AccountId {
default_accounts().charlie
}
fn init_contract() -> Contract {
let mut erc = Contract::new();
erc.balances.insert((alice(), 1), &10);
erc.balances.insert((alice(), 2), &20);
erc.balances.insert((bob(), 1), &10);
erc
}
#[ink::test]
fn can_get_correct_balance_of() {
let erc = init_contract();
assert_eq!(erc.balance_of(alice(), 1), 10);
assert_eq!(erc.balance_of(alice(), 2), 20);
assert_eq!(erc.balance_of(alice(), 3), 0);
assert_eq!(erc.balance_of(bob(), 2), 0);
}
#[ink::test]
fn can_get_correct_batch_balance_of() {
let erc = init_contract();
assert_eq!(
erc.balance_of_batch(vec![alice()], vec![1, 2, 3]),
vec![10, 20, 0]
);
assert_eq!(
erc.balance_of_batch(vec![alice(), bob()], vec![1]),
vec![10, 10]
);
assert_eq!(
erc.balance_of_batch(vec![alice(), bob(), charlie()], vec![1, 2]),
vec![10, 20, 10, 0, 0, 0]
);
}
#[ink::test]
fn can_send_tokens_between_accounts() {
let mut erc = init_contract();
assert!(erc.safe_transfer_from(alice(), bob(), 1, 5, vec![]).is_ok());
assert_eq!(erc.balance_of(alice(), 1), 5);
assert_eq!(erc.balance_of(bob(), 1), 15);
assert!(erc.safe_transfer_from(alice(), bob(), 2, 5, vec![]).is_ok());
assert_eq!(erc.balance_of(alice(), 2), 15);
assert_eq!(erc.balance_of(bob(), 2), 5);
}
#[ink::test]
fn sending_too_many_tokens_fails() {
let mut erc = init_contract();
let res = erc.safe_transfer_from(alice(), bob(), 1, 99, vec![]);
assert_eq!(res.unwrap_err(), Error::InsufficientBalance);
}
#[ink::test]
fn sending_tokens_to_zero_address_fails() {
let burn: AccountId = [0; 32].into();
let mut erc = init_contract();
let res = erc.safe_transfer_from(alice(), burn, 1, 10, vec![]);
assert_eq!(res.unwrap_err(), Error::ZeroAddressTransfer);
}
#[ink::test]
fn can_send_batch_tokens() {
let mut erc = init_contract();
assert!(erc
.safe_batch_transfer_from(alice(), bob(), vec![1, 2], vec![5, 10], vec![])
.is_ok());
let balances = erc.balance_of_batch(vec![alice(), bob()], vec![1, 2]);
assert_eq!(balances, vec![5, 10, 15, 10])
}
#[ink::test]
fn rejects_batch_if_lengths_dont_match() {
let mut erc = init_contract();
let res = erc.safe_batch_transfer_from(
alice(),
bob(),
vec![1, 2, 3],
vec![5],
vec![],
);
assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch);
}
#[ink::test]
fn batch_transfers_fail_if_len_is_zero() {
let mut erc = init_contract();
let res =
erc.safe_batch_transfer_from(alice(), bob(), vec![], vec![], vec![]);
assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch);
}
#[ink::test]
fn operator_can_send_tokens() {
let mut erc = init_contract();
let owner = alice();
let operator = bob();
set_sender(owner);
assert!(erc.set_approval_for_all(operator, true).is_ok());
set_sender(operator);
assert!(erc
.safe_transfer_from(owner, charlie(), 1, 5, vec![])
.is_ok());
assert_eq!(erc.balance_of(alice(), 1), 5);
assert_eq!(erc.balance_of(charlie(), 1), 5);
}
#[ink::test]
fn approvals_work() {
let mut erc = init_contract();
let owner = alice();
let operator = bob();
let another_operator = charlie();
set_sender(owner);
assert!(!erc.is_approved_for_all(owner, operator));
assert!(erc.set_approval_for_all(operator, true).is_ok());
assert!(erc.is_approved_for_all(owner, operator));
assert!(erc.set_approval_for_all(another_operator, true).is_ok());
assert!(erc.is_approved_for_all(owner, another_operator));
assert!(erc.set_approval_for_all(operator, false).is_ok());
assert!(!erc.is_approved_for_all(owner, operator));
}
#[ink::test]
fn minting_tokens_works() {
let mut erc = Contract::new();
set_sender(alice());
assert_eq!(erc.create(0), 1);
assert_eq!(erc.balance_of(alice(), 1), 0);
assert!(erc.mint(1, 123).is_ok());
assert_eq!(erc.balance_of(alice(), 1), 123);
}
#[ink::test]
fn minting_not_allowed_for_nonexistent_tokens() {
let mut erc = Contract::new();
let res = erc.mint(1, 123);
assert_eq!(res.unwrap_err(), Error::UnexistentToken);
}
}
}