use crate::access::Ownable;
use crate::erc1155::erc1155_base::Erc1155Base;
use crate::erc1155::errors::Error;
use crate::erc1155::events::{TransferBatch, TransferSingle};
use crate::erc1155::Erc1155;
use odra::contract_env::{caller, revert};
use odra::prelude::vec::Vec;
use odra::types::event::OdraEvent;
use odra::types::Address;
use odra::types::Bytes;
use odra::types::U256;
#[odra::module(events = [TransferBatch, TransferSingle])]
pub struct Erc1155Token {
core: Erc1155Base,
ownable: Ownable
}
#[odra::module]
impl OwnedErc1155 for Erc1155Token {
#[odra(init)]
pub fn init(&mut self) {
self.ownable.init();
}
pub fn balance_of(&self, owner: &Address, id: &U256) -> U256 {
self.core.balance_of(owner, id)
}
pub fn balance_of_batch(&self, owners: &[Address], ids: &[U256]) -> Vec<U256> {
self.core.balance_of_batch(owners, ids)
}
pub fn set_approval_for_all(&mut self, operator: &Address, approved: bool) {
self.core.set_approval_for_all(operator, approved);
}
pub fn is_approved_for_all(&self, owner: &Address, operator: &Address) -> bool {
self.core.is_approved_for_all(owner, operator)
}
pub fn safe_transfer_from(
&mut self,
from: &Address,
to: &Address,
id: &U256,
amount: &U256,
data: &Option<Bytes>
) {
self.core.safe_transfer_from(from, to, id, amount, data);
}
pub fn safe_batch_transfer_from(
&mut self,
from: &Address,
to: &Address,
ids: &[U256],
amounts: &[U256],
data: &Option<Bytes>
) {
self.core
.safe_batch_transfer_from(from, to, ids, amounts, data);
}
pub fn renounce_ownership(&mut self) {
self.ownable.renounce_ownership();
}
pub fn transfer_ownership(&mut self, new_owner: &Address) {
self.ownable.transfer_ownership(new_owner);
}
pub fn owner(&self) -> Address {
self.ownable.get_owner()
}
pub fn mint(&mut self, to: &Address, id: &U256, amount: &U256, data: &Option<Bytes>) {
let caller = caller();
self.ownable.assert_owner(&caller);
let current_balance = self.core.balances.get_instance(to).get_or_default(id);
self.core
.balances
.get_instance(to)
.set(id, *amount + current_balance);
TransferSingle {
operator: Some(caller),
from: None,
to: Some(*to),
id: *id,
value: *amount
}
.emit();
self.core
.safe_transfer_acceptance_check(&caller, &caller, to, id, amount, data);
}
pub fn mint_batch(
&mut self,
to: &Address,
ids: &[U256],
amounts: &[U256],
data: &Option<Bytes>
) {
if ids.len() != amounts.len() {
revert(Error::IdsAndAmountsLengthMismatch)
}
let caller = caller();
self.ownable.assert_owner(&caller);
for (id, amount) in ids.iter().zip(amounts.iter()) {
let current_balance = self.core.balances.get_instance(to).get_or_default(id);
self.core
.balances
.get_instance(to)
.set(id, *amount + current_balance);
}
TransferBatch {
operator: Some(caller),
from: None,
to: Some(*to),
ids: ids.to_vec(),
values: amounts.to_vec()
}
.emit();
self.core
.safe_batch_transfer_acceptance_check(&caller, &caller, to, ids, amounts, data);
}
pub fn burn(&mut self, from: &Address, id: &U256, amount: &U256) {
let caller = caller();
self.ownable.assert_owner(&caller);
let current_balance = self.core.balances.get_instance(from).get_or_default(id);
if current_balance < *amount {
revert(Error::InsufficientBalance)
}
self.core
.balances
.get_instance(from)
.set(id, current_balance - *amount);
TransferSingle {
operator: Some(caller),
from: Some(*from),
to: None,
id: *id,
value: *amount
}
.emit();
}
pub fn burn_batch(&mut self, from: &Address, ids: &[U256], amounts: &[U256]) {
if ids.len() != amounts.len() {
revert(Error::IdsAndAmountsLengthMismatch)
}
let caller = caller();
self.ownable.assert_owner(&caller);
for (id, amount) in ids.iter().zip(amounts.iter()) {
let current_balance = self.core.balances.get_instance(from).get_or_default(id);
if current_balance < *amount {
revert(Error::InsufficientBalance)
}
self.core
.balances
.get_instance(from)
.set(id, current_balance - *amount);
}
TransferBatch {
operator: Some(caller),
from: Some(*from),
to: None,
ids: ids.to_vec(),
values: amounts.to_vec()
}
.emit();
}
}
#[cfg(test)]
mod tests {
use crate::erc1155::errors::Error;
use crate::erc1155::events::{ApprovalForAll, TransferBatch, TransferSingle};
use crate::erc1155_receiver::events::{BatchReceived, SingleReceived};
use crate::erc1155_receiver::Erc1155ReceiverDeployer;
use crate::erc1155_token::{Erc1155TokenDeployer, Erc1155TokenRef};
use crate::wrapped_native::WrappedNativeTokenDeployer;
use odra::prelude::{string::ToString, vec};
use odra::test_env::assert_exception;
use odra::types::VmError::NoSuchMethod;
use odra::types::{Address, Bytes, OdraError, U256};
use odra::{assert_events, test_env};
struct TokenEnv {
token: Erc1155TokenRef,
alice: Address,
bob: Address,
owner: Address
}
fn setup() -> TokenEnv {
TokenEnv {
token: Erc1155TokenDeployer::init(),
alice: test_env::get_account(1),
bob: test_env::get_account(2),
owner: test_env::get_account(0)
}
}
#[test]
fn mint() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 100.into());
let contract = env.token;
assert_events!(
contract,
TransferSingle {
operator: Some(env.owner),
from: None,
to: Some(env.alice),
id: U256::one(),
value: 100.into()
}
);
}
#[test]
fn mint_batch() {
let mut env = setup();
env.token.mint_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[100.into(), 200.into()],
&None
);
let contract = Erc1155TokenRef::at(env.token.address());
assert_events!(
contract,
TransferBatch {
operator: Some(env.owner),
from: None,
to: Some(env.alice),
ids: vec![U256::one(), U256::from(2)],
values: vec![100.into(), 200.into()]
}
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 100.into());
assert_eq!(env.token.balance_of(&env.alice, &U256::from(2)), 200.into());
}
#[test]
fn mint_batch_errors() {
assert_exception(Error::IdsAndAmountsLengthMismatch, || {
let mut env = setup();
env.token.mint_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[100.into()],
&None
);
});
}
#[test]
fn burn() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token.burn(&env.alice, &U256::one(), &50.into());
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 50.into());
let contract = env.token;
assert_events!(
contract,
TransferSingle {
operator: Some(env.owner),
from: Some(env.alice),
to: None,
id: U256::one(),
value: 50.into()
}
);
}
#[test]
fn burn_errors() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
assert_exception(Error::InsufficientBalance, || {
env.token.burn(&env.alice, &U256::one(), &150.into());
});
let mut env = setup();
assert_exception(Error::InsufficientBalance, || {
env.token.burn(&env.alice, &U256::one(), &150.into());
});
}
#[test]
fn burn_batch() {
let mut env = setup();
env.token.mint_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[100.into(), 200.into()],
&None
);
env.token.burn_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[50.into(), 100.into()]
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 50.into());
assert_eq!(env.token.balance_of(&env.alice, &U256::from(2)), 100.into());
let contract = env.token;
assert_events!(
contract,
TransferBatch {
operator: Some(env.owner),
from: Some(env.alice),
to: None,
ids: vec![U256::one(), U256::from(2)],
values: vec![50.into(), 100.into()]
}
);
}
#[test]
fn burn_batch_errors() {
assert_exception(Error::IdsAndAmountsLengthMismatch, || {
let mut env = setup();
env.token.mint_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[100.into(), 200.into()],
&None
);
env.token
.burn_batch(&env.alice, &[U256::one(), U256::from(2)], &[50.into()]);
});
assert_exception(Error::InsufficientBalance, || {
let mut env = setup();
env.token.mint_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[100.into(), 200.into()],
&None
);
env.token.burn_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[150.into(), 300.into()]
);
});
assert_exception(Error::InsufficientBalance, || {
let mut env = setup();
env.token.burn_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[150.into(), 300.into()]
);
});
}
#[test]
fn balance_of() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &200.into(), &None);
env.token.mint(&env.bob, &U256::one(), &300.into(), &None);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 100.into());
assert_eq!(env.token.balance_of(&env.alice, &U256::from(2)), 200.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::one()), 300.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::from(2)), 0.into());
}
#[test]
fn balance_of_batch() {
let mut env = setup();
env.token.mint_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[100.into(), 200.into()],
&None
);
env.token.mint_batch(
&env.bob,
&[U256::one(), U256::from(2)],
&[300.into(), 400.into()],
&None
);
assert_eq!(
env.token.balance_of_batch(
&[env.alice, env.alice, env.alice, env.bob, env.bob, env.bob],
&[
U256::one(),
U256::from(2),
U256::from(3),
U256::one(),
U256::from(2),
U256::from(3)
]
),
vec![
U256::from(100),
U256::from(200),
U256::zero(),
U256::from(300),
U256::from(400),
U256::zero()
]
);
}
#[test]
fn balance_of_batch_errors() {
assert_exception(Error::AccountsAndIdsLengthMismatch, || {
let mut env = setup();
env.token.mint_batch(
&env.alice,
&[U256::one(), U256::from(2)],
&[100.into(), 200.into()],
&None
);
env.token.balance_of_batch(
&[env.alice, env.alice, env.alice],
&[U256::one(), U256::from(2)]
);
});
}
#[test]
fn set_approval_for_all() {
let mut env = setup();
test_env::set_caller(env.alice);
env.token.set_approval_for_all(&env.bob, true);
assert!(env.token.is_approved_for_all(&env.alice, &env.bob));
let contract = env.token;
assert_events!(
contract,
ApprovalForAll {
owner: env.alice,
operator: env.bob,
approved: true
}
);
}
#[test]
fn unset_approval_for_all() {
let mut env = setup();
test_env::set_caller(env.alice);
env.token.set_approval_for_all(&env.bob, true);
test_env::set_caller(env.alice);
env.token.set_approval_for_all(&env.bob, false);
assert!(!env.token.is_approved_for_all(&env.alice, &env.bob));
let contract = env.token;
assert_events!(
contract,
ApprovalForAll {
owner: env.alice,
operator: env.bob,
approved: false
}
);
}
#[test]
fn set_approval_to_self() {
assert_exception(Error::ApprovalForSelf, || {
let mut env = setup();
test_env::set_caller(env.alice);
env.token.set_approval_for_all(&env.alice, true);
});
}
#[test]
fn safe_transfer_from() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
test_env::set_caller(env.alice);
env.token
.safe_transfer_from(&env.alice, &env.bob, &U256::one(), &50.into(), &None);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 50.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::one()), 50.into());
let contract = env.token;
assert_events!(
contract,
TransferSingle {
operator: Some(env.alice),
from: Some(env.alice),
to: Some(env.bob),
id: U256::one(),
value: 50.into()
}
);
}
#[test]
fn safe_transfer_from_approved() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
test_env::set_caller(env.alice);
env.token.set_approval_for_all(&env.bob, true);
test_env::set_caller(env.bob);
env.token
.safe_transfer_from(&env.alice, &env.bob, &U256::one(), &50.into(), &None);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 50.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::one()), 50.into());
let contract = env.token;
assert_events!(
contract,
TransferSingle {
operator: Some(env.bob),
from: Some(env.alice),
to: Some(env.bob),
id: U256::one(),
value: 50.into()
}
);
}
#[test]
fn safe_transfer_from_errors() {
assert_exception(Error::InsufficientBalance, || {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
test_env::set_caller(env.alice);
env.token
.safe_transfer_from(&env.alice, &env.bob, &U256::one(), &200.into(), &None);
});
assert_exception(Error::NotAnOwnerOrApproved, || {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
test_env::set_caller(env.bob);
env.token
.safe_transfer_from(&env.alice, &env.bob, &U256::one(), &100.into(), &None);
});
}
#[test]
fn safe_batch_transfer_from() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &200.into(), &None);
test_env::set_caller(env.alice);
env.token.safe_batch_transfer_from(
&env.alice,
&env.bob,
&[U256::one(), U256::from(2)],
&[50.into(), 100.into()],
&None
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 50.into());
assert_eq!(env.token.balance_of(&env.alice, &U256::from(2)), 100.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::one()), 50.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::from(2)), 100.into());
let contract = env.token;
assert_events!(
contract,
TransferBatch {
operator: Some(env.alice),
from: Some(env.alice),
to: Some(env.bob),
ids: vec![U256::one(), U256::from(2)],
values: vec![50.into(), 100.into()]
}
);
}
#[test]
fn safe_batch_transfer_from_approved() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &200.into(), &None);
test_env::set_caller(env.alice);
env.token.set_approval_for_all(&env.bob, true);
test_env::set_caller(env.bob);
env.token.safe_batch_transfer_from(
&env.alice,
&env.bob,
&[U256::one(), U256::from(2)],
&[50.into(), 100.into()],
&None
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 50.into());
assert_eq!(env.token.balance_of(&env.alice, &U256::from(2)), 100.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::one()), 50.into());
assert_eq!(env.token.balance_of(&env.bob, &U256::from(2)), 100.into());
let contract = env.token;
assert_events!(
contract,
TransferBatch {
operator: Some(env.bob),
from: Some(env.alice),
to: Some(env.bob),
ids: vec![U256::one(), U256::from(2)],
values: vec![50.into(), 100.into()]
}
);
}
#[test]
fn safe_batch_transfer_errors() {
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &200.into(), &None);
assert_exception(Error::InsufficientBalance, || {
test_env::set_caller(env.alice);
env.token.safe_batch_transfer_from(
&env.alice,
&env.bob,
&[U256::one(), U256::from(2)],
&[50.into(), 300.into()],
&None
);
});
let mut env = setup();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &200.into(), &None);
assert_exception(Error::NotAnOwnerOrApproved, || {
test_env::set_caller(env.bob);
env.token.safe_batch_transfer_from(
&env.alice,
&env.bob,
&[U256::one(), U256::from(2)],
&[50.into(), 100.into()],
&None
);
});
}
#[test]
fn safe_transfer_to_valid_receiver() {
let mut env = setup();
let receiver = Erc1155ReceiverDeployer::default();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
test_env::set_caller(env.alice);
env.token.safe_transfer_from(
&env.alice,
receiver.address(),
&U256::one(),
&100.into(),
&None
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 0.into());
assert_eq!(
env.token.balance_of(receiver.address(), &U256::one()),
100.into()
);
assert_events!(
receiver,
SingleReceived {
operator: Some(env.alice),
from: Some(env.alice),
token_id: U256::one(),
amount: 100.into(),
data: None
}
);
}
#[test]
fn safe_transfer_to_valid_receiver_with_data() {
let mut env = setup();
let receiver = Erc1155ReceiverDeployer::default();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
test_env::set_caller(env.alice);
env.token.safe_transfer_from(
&env.alice,
receiver.address(),
&U256::one(),
&100.into(),
&Some(Bytes::from(b"data".to_vec()))
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 0.into());
assert_eq!(
env.token.balance_of(receiver.address(), &U256::one()),
100.into()
);
assert_events!(
receiver,
SingleReceived {
operator: Some(env.alice),
from: Some(env.alice),
token_id: U256::one(),
amount: 100.into(),
data: Some(Bytes::from(b"data".to_vec()))
}
);
}
#[test]
fn safe_transfer_to_invalid_receiver() {
assert_exception(
OdraError::VmError(NoSuchMethod("on_erc1155_received".to_string())),
|| {
let mut env = setup();
let receiver = WrappedNativeTokenDeployer::init();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
test_env::set_caller(env.alice);
env.token.safe_transfer_from(
&env.alice,
receiver.address(),
&U256::one(),
&100.into(),
&None
);
}
);
}
#[test]
fn safe_batch_transfer_to_valid_receiver() {
let mut env = setup();
let receiver = Erc1155ReceiverDeployer::default();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &100.into(), &None);
test_env::set_caller(env.alice);
env.token.safe_batch_transfer_from(
&env.alice,
receiver.address(),
&[U256::one(), U256::from(2)],
&[100.into(), 100.into()],
&None
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 0.into());
assert_eq!(
env.token.balance_of(receiver.address(), &U256::one()),
100.into()
);
assert_eq!(env.token.balance_of(&env.alice, &U256::from(2)), 0.into());
assert_eq!(
env.token.balance_of(receiver.address(), &U256::from(2)),
100.into()
);
assert_events!(
receiver,
BatchReceived {
operator: Some(env.alice),
from: Some(env.alice),
token_ids: vec![U256::one(), U256::from(2)],
amounts: vec![100.into(), 100.into()],
data: None
}
);
}
#[test]
fn safe_batch_transfer_to_valid_receiver_with_data() {
let mut env = setup();
let receiver = Erc1155ReceiverDeployer::default();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &100.into(), &None);
test_env::set_caller(env.alice);
env.token.safe_batch_transfer_from(
&env.alice,
receiver.address(),
&[U256::one(), U256::from(2)],
&[100.into(), 100.into()],
&Some(Bytes::from(b"data".to_vec()))
);
assert_eq!(env.token.balance_of(&env.alice, &U256::one()), 0.into());
assert_eq!(
env.token.balance_of(receiver.address(), &U256::one()),
100.into()
);
assert_eq!(env.token.balance_of(&env.alice, &U256::from(2)), 0.into());
assert_eq!(
env.token.balance_of(receiver.address(), &U256::from(2)),
100.into()
);
assert_events!(
receiver,
BatchReceived {
operator: Some(env.alice),
from: Some(env.alice),
token_ids: vec![U256::one(), U256::from(2)],
amounts: vec![100.into(), 100.into()],
data: Some(Bytes::from(b"data".to_vec()))
}
);
}
#[test]
fn safe_batch_transfer_to_invalid_receiver() {
assert_exception(
OdraError::VmError(NoSuchMethod("on_erc1155_batch_received".to_string())),
|| {
let mut env = setup();
let receiver = WrappedNativeTokenDeployer::init();
env.token.mint(&env.alice, &U256::one(), &100.into(), &None);
env.token
.mint(&env.alice, &U256::from(2), &100.into(), &None);
test_env::set_caller(env.alice);
env.token.safe_batch_transfer_from(
&env.alice,
receiver.address(),
&[U256::one(), U256::from(2)],
&[100.into(), 100.into()],
&None
);
}
);
}
}