use bnum::types::U256;
use super::access_control::{AccessControl, AccessControlError, Role};
#[allow(unused_imports)]
use crate as casper_contract_sdk;
use crate::{collections::Map, macros::blake2b256, prelude::*};
#[derive(Debug, PartialEq, Eq)]
#[casper]
pub enum Cep18Error {
InvalidContext,
InsufficientBalance,
InsufficientAllowance,
Overflow,
PackageHashMissing,
PackageHashNotPackage,
InvalidEventsMode,
MissingEventsMode,
Phantom,
FailedToGetArgBytes,
InsufficientRights,
InvalidAdminList,
InvalidMinterList,
InvalidNoneList,
InvalidEnableMBFlag,
AlreadyInitialized,
MintBurnDisabled,
CannotTargetSelfUser,
InvalidBurnTarget,
}
impl From<AccessControlError> for Cep18Error {
fn from(error: AccessControlError) -> Self {
match error {
AccessControlError::NotAuthorized => Cep18Error::InsufficientRights,
}
}
}
#[casper(message, path = crate)]
pub struct Transfer {
pub from: Option<Entity>,
pub to: Entity,
pub amount: U256,
}
#[casper(message, path = crate)]
pub struct Approve {
pub owner: Entity,
pub spender: Entity,
pub amount: U256,
}
pub const ADMIN_ROLE: Role = blake2b256!("admin");
pub const MINTER_ROLE: Role = blake2b256!("minter");
#[casper(path = crate)]
pub struct CEP18State {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub total_supply: U256,
pub balances: Map<Entity, U256>,
pub allowances: Map<(Entity, Entity), U256>,
pub enable_mint_burn: bool,
}
impl CEP18State {
fn transfer_balance(
&mut self,
sender: &Entity,
recipient: &Entity,
amount: U256,
) -> Result<(), Cep18Error> {
if amount.is_zero() {
return Ok(());
}
let sender_balance = self.balances.get(sender).unwrap_or_default();
let new_sender_balance = sender_balance
.checked_sub(amount)
.ok_or(Cep18Error::InsufficientBalance)?;
let recipient_balance = self.balances.get(recipient).unwrap_or_default();
let new_recipient_balance = recipient_balance
.checked_add(amount)
.ok_or(Cep18Error::Overflow)?;
self.balances.insert(sender, &new_sender_balance);
self.balances.insert(recipient, &new_recipient_balance);
Ok(())
}
}
impl CEP18State {
pub fn new(name: &str, symbol: &str, decimals: u8, total_supply: U256) -> CEP18State {
CEP18State {
name: name.to_string(),
symbol: symbol.to_string(),
decimals,
total_supply,
balances: Map::new("balances"),
allowances: Map::new("allowances"),
enable_mint_burn: false,
}
}
}
#[casper(path = crate, export = true)]
pub trait CEP18 {
#[casper(private)]
fn state(&self) -> &CEP18State;
#[casper(private)]
fn state_mut(&mut self) -> &mut CEP18State;
fn name(&self) -> &str {
&self.state().name
}
fn symbol(&self) -> &str {
&self.state().symbol
}
fn decimals(&self) -> u8 {
self.state().decimals
}
fn total_supply(&self) -> U256 {
self.state().total_supply
}
fn balance_of(&self, address: Entity) -> U256 {
self.state().balances.get(&address).unwrap_or_default()
}
fn allowance(&self, spender: Entity, owner: Entity) {
self.state()
.allowances
.get(&(spender, owner))
.unwrap_or_default();
}
#[casper(revert_on_error)]
fn approve(&mut self, spender: Entity, amount: U256) -> Result<(), Cep18Error> {
let owner = casper::get_caller();
if owner == spender {
return Err(Cep18Error::CannotTargetSelfUser);
}
let lookup_key = (owner, spender);
self.state_mut().allowances.insert(&lookup_key, &amount);
casper::emit(Approve {
owner,
spender,
amount,
})
.expect("failed to emit message");
Ok(())
}
#[casper(revert_on_error)]
fn decrease_allowance(&mut self, spender: Entity, amount: U256) -> Result<(), Cep18Error> {
let owner = casper::get_caller();
if owner == spender {
return Err(Cep18Error::CannotTargetSelfUser);
}
let lookup_key = (owner, spender);
let allowance = self.state().allowances.get(&lookup_key).unwrap_or_default();
let allowance = allowance.saturating_sub(amount);
self.state_mut().allowances.insert(&lookup_key, &allowance);
Ok(())
}
#[casper(revert_on_error)]
fn increase_allowance(&mut self, spender: Entity, amount: U256) -> Result<(), Cep18Error> {
let owner = casper::get_caller();
if owner == spender {
return Err(Cep18Error::CannotTargetSelfUser);
}
let lookup_key = (owner, spender);
let allowance = self.state().allowances.get(&lookup_key).unwrap_or_default();
let allowance = allowance.saturating_add(amount);
self.state_mut().allowances.insert(&lookup_key, &allowance);
Ok(())
}
#[casper(revert_on_error)]
fn transfer(&mut self, recipient: Entity, amount: U256) -> Result<(), Cep18Error> {
let sender = casper::get_caller();
if sender == recipient {
return Err(Cep18Error::CannotTargetSelfUser);
}
self.state_mut()
.transfer_balance(&sender, &recipient, amount)?;
casper::emit(Transfer {
from: Some(sender),
to: recipient,
amount,
})
.expect("failed to emit message");
Ok(())
}
#[casper(revert_on_error)]
fn transfer_from(
&mut self,
owner: Entity,
recipient: Entity,
amount: U256,
) -> Result<(), Cep18Error> {
let spender = casper::get_caller();
if owner == recipient {
return Err(Cep18Error::CannotTargetSelfUser);
}
if amount.is_zero() {
return Ok(());
}
let spender_allowance = self
.state()
.allowances
.get(&(owner, spender))
.unwrap_or_default();
let new_spender_allowance = spender_allowance
.checked_sub(amount)
.ok_or(Cep18Error::InsufficientAllowance)?;
self.state_mut()
.transfer_balance(&owner, &recipient, amount)?;
self.state_mut()
.allowances
.insert(&(owner, spender), &new_spender_allowance);
casper::emit(Transfer {
from: Some(owner),
to: recipient,
amount,
})
.expect("failed to emit message");
Ok(())
}
}
#[casper(path = crate, export = true)]
pub trait Mintable: CEP18 + AccessControl {
#[casper(revert_on_error)]
fn mint(&mut self, owner: Entity, amount: U256) -> Result<(), Cep18Error> {
if !CEP18::state(self).enable_mint_burn {
return Err(Cep18Error::MintBurnDisabled);
}
AccessControl::require_any_role(self, &[ADMIN_ROLE, MINTER_ROLE])?;
let balance = CEP18::state(self).balances.get(&owner).unwrap_or_default();
let new_balance = balance.checked_add(amount).ok_or(Cep18Error::Overflow)?;
CEP18::state_mut(self).balances.insert(&owner, &new_balance);
CEP18::state_mut(self).total_supply = CEP18::state(self)
.total_supply
.checked_add(amount)
.ok_or(Cep18Error::Overflow)?;
casper::emit(Transfer {
from: None,
to: owner,
amount,
})
.expect("failed to emit message");
Ok(())
}
}
#[casper(path = crate, export = true)]
pub trait Burnable: CEP18 {
#[casper(revert_on_error)]
fn burn(&mut self, owner: Entity, amount: U256) -> Result<(), Cep18Error> {
if !self.state().enable_mint_burn {
return Err(Cep18Error::MintBurnDisabled);
}
if owner != casper::get_caller() {
return Err(Cep18Error::InvalidBurnTarget);
}
let balance = self.state().balances.get(&owner).unwrap_or_default();
let new_balance = balance.checked_add(amount).ok_or(Cep18Error::Overflow)?;
self.state_mut().balances.insert(&owner, &new_balance);
self.state_mut().total_supply = self
.state()
.total_supply
.checked_sub(amount)
.ok_or(Cep18Error::Overflow)?;
Ok(())
}
}