Skip to content

Commit

Permalink
feat(psp22): protect with owner
Browse files Browse the repository at this point in the history
  • Loading branch information
chungquantin committed Oct 31, 2024
1 parent 763c6cc commit 411ff66
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 126 deletions.
3 changes: 3 additions & 0 deletions pop-api/examples/fungibles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# PSP22 Example Contract

The contract is an example contract following the PSP22 standard built with Pop API Fungibles.
112 changes: 49 additions & 63 deletions pop-api/examples/fungibles/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ use pop_api::{
Psp22Error,
},
};
use traits::MinterRole;

#[cfg(test)]
mod tests;
mod traits;

#[ink::contract]
mod fungibles {
Expand All @@ -26,24 +24,24 @@ mod fungibles {
#[ink(storage)]
pub struct Fungible {
id: TokenId,
minters: Mapping<AccountId, ()>,
owner: Option<AccountId>,
}

impl Fungible {
/// Instantiate the contract and wrap an existing token.
///
/// # Parameters
/// * - `id` - The token.
/// * - `minter` - The account that has a permission to mint tokens.
#[ink(constructor, payable)]
pub fn existing(id: TokenId, minter: AccountId) -> Result<Self, Psp22Error> {
pub fn existing(id: TokenId) -> Result<Self, Psp22Error> {
// Make sure token exists.
if !api::token_exists(id).unwrap_or_default() {
return Err(Psp22Error::Custom(String::from("Token does not exist")));
}
let mut minters = Mapping::new();
minters.insert(minter, &());
Ok(Self { id, minters })
let mut instance = Self { id, owner: None };
let contract_id = instance.env().account_id();
instance.owner = Some(instance.env().caller());
Ok(instance)
}

/// Instantiate the contract and create a new token. The token identifier will be stored
Expand All @@ -52,26 +50,38 @@ mod fungibles {
/// # Parameters
/// * - `id` - The identifier of the token.
/// * - `min_balance` - The minimum balance required for accounts holding this token.
/// * - `minter` - The account that has a permission to mint tokens.
// The `min_balance` ensures accounts hold a minimum amount of tokens, preventing tiny,
// inactive balances from bloating the blockchain state and slowing down the network.
#[ink(constructor, payable)]
pub fn new(
id: TokenId,
min_balance: Balance,
minter: AccountId,
) -> Result<Self, Psp22Error> {
let mut minters = Mapping::new();
minters.insert(minter, &());

let instance = Self { id, minters };
pub fn new(id: TokenId, min_balance: Balance) -> Result<Self, Psp22Error> {
let mut instance = Self { id, owner: None };
let contract_id = instance.env().account_id();
instance.owner = Some(instance.env().caller());
api::create(id, contract_id, min_balance).map_err(Psp22Error::from)?;
instance
.env()
.emit_event(Created { id, creator: contract_id, admin: contract_id });
Ok(instance)
}

/// Check if a caller is an owner of the contract.
fn ensure_owner(&self) -> Result<(), Psp22Error> {
if self.owner != Some(self.env().caller()) {
return Err(Psp22Error::Custom(String::from("Not an owner")));
}
Ok(())
}

/// Set an owner of the contract.
///
/// # Parameters
/// - `owner` - New owner account.
#[ink(message)]
pub fn set_owner(&mut self, owner: AccountId) -> Result<(), Psp22Error> {
self.ensure_owner()?;
self.owner = Some(owner);
Ok(())
}
}

impl Psp22 for Fungible {
Expand Down Expand Up @@ -100,8 +110,8 @@ mod fungibles {
api::allowance(self.id, owner, spender).unwrap_or_default()
}

/// Transfers `value` amount of tokens from the caller's account to account `to` with
/// additional `data` in unspecified format. Contract must be pre-approved by the `caller`.
/// Transfers `value` amount of tokens from the contract to account `to` with
/// additional `data` in unspecified format.
///
/// # Parameters
/// - `to` - The recipient account.
Expand All @@ -114,14 +124,15 @@ mod fungibles {
value: Balance,
_data: Vec<u8>,
) -> Result<(), Psp22Error> {
let caller = self.env().caller();
// No-op if the caller and `to` is the same address or `value` is zero.
if caller == to || value == 0 {
self.ensure_owner()?;
let contract = self.env().account_id();

// No-op if the contract and `to` is the same address or `value` is zero.
if contract == to || value == 0 {
return Ok(());
}
// Contract is pre-approved to transfer from the `caller`.
api::transfer_from(self.id, caller, to, value).map_err(Psp22Error::from)?;
self.env().emit_event(Transfer { from: Some(caller), to: Some(to), value });
api::transfer(self.id, to, value).map_err(Psp22Error::from)?;
self.env().emit_event(Transfer { from: Some(contract), to: Some(to), value });
Ok(())
}

Expand All @@ -141,16 +152,18 @@ mod fungibles {
value: Balance,
_data: Vec<u8>,
) -> Result<(), Psp22Error> {
self.ensure_owner()?;
let contract = self.env().account_id();

// No-op if `from` and `to` is the same address or `value` is zero.
if from == to || value == 0 {
return Ok(());
}
// If `from` and the contract are different addresses, a successful transfer results
// in decreased allowance by `from` to `to` and an `Approval` event with
// in decreased allowance by `from` to the contract and an `Approval` event with
// the new allowance amount is emitted.
api::transfer_from(self.id, from, to, value).map_err(Psp22Error::from)?;
self.env().emit_event(Transfer { from: Some(from), to: Some(to), value });
self.env().emit_event(Transfer { from: Some(contract), to: Some(to), value });
self.env().emit_event(Approval {
owner: from,
spender: contract,
Expand All @@ -168,7 +181,9 @@ mod fungibles {
/// - `value` - The number of tokens to approve.
#[ink(message)]
fn approve(&mut self, spender: AccountId, value: Balance) -> Result<(), Psp22Error> {
self.ensure_owner()?;
let contract = self.env().account_id();

// No-op if the contract and `spender` is the same address.
if contract == spender {
return Ok(());
Expand All @@ -189,7 +204,9 @@ mod fungibles {
spender: AccountId,
value: Balance,
) -> Result<(), Psp22Error> {
self.ensure_owner()?;
let contract = self.env().account_id();

// No-op if the contract and `spender` is the same address or `value` is zero.
if contract == spender || value == 0 {
return Ok(());
Expand All @@ -211,7 +228,9 @@ mod fungibles {
spender: AccountId,
value: Balance,
) -> Result<(), Psp22Error> {
self.ensure_owner()?;
let contract = self.env().account_id();

// No-op if the contract and `spender` is the same address or `value` is zero.
if contract == spender || value == 0 {
return Ok(());
Expand Down Expand Up @@ -256,7 +275,7 @@ mod fungibles {
/// - `value` - The number of tokens to mint.
#[ink(message)]
fn mint(&mut self, account: AccountId, value: Balance) -> Result<(), Psp22Error> {
self.ensure_minter(account)?;
self.ensure_owner()?;
// No-op if `value` is zero.
if value == 0 {
return Ok(());
Expand All @@ -275,6 +294,7 @@ mod fungibles {
/// - `value` - The number of tokens to destroy.
#[ink(message)]
fn burn(&mut self, account: AccountId, value: Balance) -> Result<(), Psp22Error> {
self.ensure_owner()?;
// No-op if `value` is zero.
if value == 0 {
return Ok(());
Expand All @@ -284,38 +304,4 @@ mod fungibles {
Ok(())
}
}

impl MinterRole for Fungible {
/// Check if the caller is the minter of the contract.
#[ink(message)]
fn ensure_minter(&self, account: AccountId) -> Result<(), Psp22Error> {
if !self.minters.contains(account) {
return Err(Psp22Error::Custom(String::from("Must be a minter")));
}
Ok(())
}

/// Add a new minter by existing minters.
///
/// # Parameters
/// - `minter` - The account that will be granted a permission to mint.
#[ink(message)]
fn add_minter(&mut self, minter: AccountId) -> Result<(), Psp22Error> {
self.ensure_minter(self.env().caller())?;
self.minters.insert(minter, &());
Ok(())
}

/// Remove a minter by existing minters.
///
/// # Parameters
/// - `minter` - The account that will be granted a permission to mint.
#[ink(message)]
fn remove_minter(&mut self, minter: AccountId) -> Result<(), Psp22Error> {
self.ensure_minter(self.env().caller())?;
self.minters.remove(minter);
Ok(())
}
}
}
Loading

0 comments on commit 411ff66

Please sign in to comment.