From 443a9409813748acc1ed0064ba8d2f4ac3aa4b1b Mon Sep 17 00:00:00 2001 From: Michal Handzlik <4hansu@gmail.com> Date: Wed, 24 Jan 2024 18:01:14 +0100 Subject: [PATCH 1/5] Move events outside of contract --- data.rs | 81 +++++++++++++++++++------------------------------------ events.rs | 19 +++++++++++++ lib.rs | 54 +++++++++---------------------------- 3 files changed, 58 insertions(+), 96 deletions(-) create mode 100644 events.rs diff --git a/data.rs b/data.rs index c65cb3c..be39d27 100644 --- a/data.rs +++ b/data.rs @@ -1,4 +1,5 @@ -use crate::PSP22Error; +use crate::errors::PSP22Error; +use crate::events::{Approval, Transfer}; use ink::prelude::string::String; use ink::{ prelude::{vec, vec::Vec}, @@ -6,21 +7,25 @@ use ink::{ storage::Mapping, }; -/// Temporary type for events emitted during operations that change the +/// Common wrapper type for events emitted during operations that change the /// state of PSP22Data struct. -/// This is meant to be replaced with proper ink! events as soon as the -/// language allows for event definitions outside contracts. pub enum PSP22Event { - Transfer { - from: Option, - to: Option, - value: u128, - }, - Approval { - owner: AccountId, - spender: AccountId, - amount: u128, - }, + Transfer(Transfer), + Approval(Approval), +} + +// Shortcut for Approval PSP22Event constructor. +fn approval_event(owner: AccountId, spender: AccountId, amount: u128) -> PSP22Event { + PSP22Event::Approval(Approval { + owner, + spender, + amount, + }) +} + +// Shortcut for Transfer PSP22Event constructor. +fn transfer_event(from: Option, to: Option, value: u128) -> PSP22Event { + PSP22Event::Transfer(Transfer { from, to, value }) } /// A class implementing the internal logic of a PSP22 token. @@ -92,11 +97,7 @@ impl PSP22Data { // Total supply is limited by u128.MAX so no overflow is possible self.balances .insert(to, &(to_balance.saturating_add(value))); - Ok(vec![PSP22Event::Transfer { - from: Some(caller), - to: Some(to), - value, - }]) + Ok(vec![transfer_event(Some(caller), Some(to), value)]) } /// Transfers `value` tokens from `from` to `to`, but using the allowance @@ -142,16 +143,8 @@ impl PSP22Data { self.balances .insert(to, &(to_balance.saturating_add(value))); Ok(vec![ - PSP22Event::Approval { - owner: from, - spender: caller, - amount: allowance.saturating_sub(value), - }, - PSP22Event::Transfer { - from: Some(from), - to: Some(to), - value, - }, + approval_event(from, caller, allowance.saturating_sub(value)), + transfer_event(Some(from), Some(to), value), ]) } @@ -171,11 +164,7 @@ impl PSP22Data { } else { self.allowances.insert((owner, spender), &value); } - Ok(vec![PSP22Event::Approval { - owner, - spender, - amount: value, - }]) + Ok(vec![approval_event(owner, spender, value)]) } /// Increases the allowance granted by `owner` to `spender` by `delta_value`. @@ -191,11 +180,7 @@ impl PSP22Data { let allowance = self.allowance(owner, spender); let amount = allowance.saturating_add(delta_value); self.allowances.insert((owner, spender), &amount); - Ok(vec![PSP22Event::Approval { - owner, - spender, - amount, - }]) + Ok(vec![approval_event(owner, spender, amount)]) } /// Decreases the allowance granted by `owner` to `spender` by `delta_value`. @@ -218,11 +203,7 @@ impl PSP22Data { } else { self.allowances.insert((owner, spender), &amount); } - Ok(vec![PSP22Event::Approval { - owner, - spender, - amount, - }]) + Ok(vec![approval_event(owner, spender, amount)]) } /// Mints a `value` of new tokens to `to` account. @@ -239,11 +220,7 @@ impl PSP22Data { self.total_supply = new_supply; let new_balance = self.balance_of(to).saturating_add(value); self.balances.insert(to, &new_balance); - Ok(vec![PSP22Event::Transfer { - from: None, - to: Some(to), - value, - }]) + Ok(vec![transfer_event(None, Some(to), value)]) } /// Burns `value` tokens from `from` account. @@ -261,10 +238,6 @@ impl PSP22Data { self.balances.insert(from, &(balance.saturating_sub(value))); } self.total_supply = self.total_supply.saturating_sub(value); - Ok(vec![PSP22Event::Transfer { - from: Some(from), - to: None, - value, - }]) + Ok(vec![transfer_event(Some(from), None, value)]) } } diff --git a/events.rs b/events.rs new file mode 100644 index 0000000..03daaa0 --- /dev/null +++ b/events.rs @@ -0,0 +1,19 @@ +use ink::primitives::AccountId; + +#[ink::event] +pub struct Approval { + #[ink(topic)] + pub owner: AccountId, + #[ink(topic)] + pub spender: AccountId, + pub amount: u128, +} + +#[ink::event] +pub struct Transfer { + #[ink(topic)] + pub from: Option, + #[ink(topic)] + pub to: Option, + pub value: u128, +} diff --git a/lib.rs b/lib.rs index a3a6c26..99205a3 100644 --- a/lib.rs +++ b/lib.rs @@ -2,11 +2,13 @@ mod data; mod errors; +mod events; mod testing; mod traits; pub use data::{PSP22Data, PSP22Event}; pub use errors::PSP22Error; +pub use events::{Approval, Transfer}; pub use traits::{PSP22Burnable, PSP22Metadata, PSP22Mintable, PSP22}; // An example code of a smart contract using PSP22Data struct to implement @@ -15,12 +17,11 @@ pub use traits::{PSP22Burnable, PSP22Metadata, PSP22Mintable, PSP22}; // Any contract can be easily enriched to act as PSP22 token by: // (1) adding PSP22Data to contract storage // (2) properly initializing it -// (3) defining the correct Transfer and Approval events -// (4) implementing PSP22 trait based on PSP22Data methods -// (5) properly emitting resulting events +// (3) implementing PSP22 trait based on PSP22Data methods +// (4) properly emitting resulting events // -// It is a good practice to also implement the optional PSP22Metadata extension (6) -// and include unit tests (7). +// It is a good practice to also implement the optional PSP22Metadata extension (5) +// and include unit tests (6). #[cfg(feature = "contract")] #[ink::contract] mod token { @@ -51,50 +52,19 @@ mod token { } } - // A helper function translating a vector of PSP22Events into the proper - // ink event types (defined internally in this contract) and emitting them. - // (5) + // A helper function emitting events contained in a vector of PSP22Events. + // (4) fn emit_events(&self, events: Vec) { for event in events { match event { - PSP22Event::Transfer { from, to, value } => { - self.env().emit_event(Transfer { from, to, value }) - } - PSP22Event::Approval { - owner, - spender, - amount, - } => self.env().emit_event(Approval { - owner, - spender, - amount, - }), + PSP22Event::Transfer(e) => self.env().emit_event(e), + PSP22Event::Approval(e) => self.env().emit_event(e), } } } } // (3) - #[ink(event)] - pub struct Approval { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - spender: AccountId, - amount: u128, - } - - // (3) - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - value: u128, - } - - // (4) impl PSP22 for Token { #[ink(message)] fn total_supply(&self) -> u128 { @@ -172,7 +142,7 @@ mod token { } } - // (6) + // (5) impl PSP22Metadata for Token { #[ink(message)] fn token_name(&self) -> Option { @@ -188,7 +158,7 @@ mod token { } } - // (7) + // (6) #[cfg(test)] mod tests { crate::tests!(Token, (|supply| Token::new(supply, None, None, 0))); From 4f9d80faa70f9daa4ca24068915daab2d88889d1 Mon Sep 17 00:00:00 2001 From: Michal Handzlik <4hansu@gmail.com> Date: Thu, 25 Jan 2024 15:36:56 +0100 Subject: [PATCH 2/5] Fix tests macro --- lib.rs | 1 + testing.rs | 104 +++++++++++++++++++++++++---------------------------- 2 files changed, 50 insertions(+), 55 deletions(-) diff --git a/lib.rs b/lib.rs index 99205a3..2ffffe7 100644 --- a/lib.rs +++ b/lib.rs @@ -161,6 +161,7 @@ mod token { // (6) #[cfg(test)] mod tests { + use super::Token; crate::tests!(Token, (|supply| Token::new(supply, None, None, 0))); } } diff --git a/testing.rs b/testing.rs index cd1f87f..550efdf 100644 --- a/testing.rs +++ b/testing.rs @@ -7,49 +7,43 @@ macro_rules! tests { ($contract:ident, $constructor:expr) => { mod psp22_unit_tests { - use super::super::*; - use ink::env::{test::*, DefaultEnvironment as E}; + use super::*; + use ink::env::test::*; + use ink::env::DefaultEnvironment as E; + use ink::primitives::AccountId; + use $crate::{Approval, PSP22Error, Transfer, PSP22}; - type Event = <$contract as ink::reflect::ContractEventBase>::Type; + // Gathers all emitted events, skip `shift` first, and return as a vector. + fn get_events(shift: usize) -> Vec { + recorded_events().skip(shift).collect() + } - // Gathers all emitted events, skip `shift` first, decode the rest and return as vector - fn decode_events(shift: usize) -> Vec { - recorded_events() - .skip(shift) - .map(|e| ::decode(&mut &e.data[..]).unwrap()) - .collect() + // Checks if the given event is a Transfer + fn is_transfer(event: &EmittedEvent) -> bool { + ::decode(&mut &event.data[..]).is_ok() } // Asserts if the given event is a Transfer with particular from_, to_ and value_ - fn assert_transfer(event: &Event, from_: AccountId, to_: AccountId, value_: u128) { - if let Event::Transfer(Transfer { from, to, value }) = event { - assert_eq!(*from, Some(from_), "Transfer event: 'from' mismatch"); - assert_eq!(*to, Some(to_), "Transfer event: 'to' mismatch"); - assert_eq!(*value, value_, "Transfer event: 'value' mismatch"); - } else { - panic!("Event is not Transfer") - } + fn assert_transfer(event: &EmittedEvent, from: AccountId, to: AccountId, value: u128) { + let e = ::decode(&mut &event.data[..]) + .expect("Event is not Transfer"); + assert_eq!(e.from, Some(from), "Transfer event: 'from' mismatch"); + assert_eq!(e.to, Some(to), "Transfer event: 'to' mismatch"); + assert_eq!(e.value, value, "Transfer event: 'value' mismatch"); } // Asserts if the given event is a Approval with particular owner_, spender_ and amount_ fn assert_approval( - event: &Event, - owner_: AccountId, - spender_: AccountId, - amount_: u128, + event: &EmittedEvent, + owner: AccountId, + spender: AccountId, + amount: u128, ) { - if let Event::Approval(Approval { - owner, - spender, - amount, - }) = event - { - assert_eq!(*owner, owner_, "Approval event: 'owner' mismatch"); - assert_eq!(*spender, spender_, "Approval event: 'spender' mismatch"); - assert_eq!(*amount, amount_, "Approval event: 'amount' mismatch"); - } else { - panic!("Event is not Approval") - } + let e = ::decode(&mut &event.data[..]) + .expect("Event is not Approval"); + assert_eq!(e.owner, owner, "Approval event: 'owner' mismatch"); + assert_eq!(e.spender, spender, "Approval event: 'spender' mismatch"); + assert_eq!(e.amount, amount, "Approval event: 'amount' mismatch"); } #[ink::test] @@ -144,7 +138,7 @@ macro_rules! tests { let start = recorded_events().count(); assert!(token.transfer(acc.bob, value, vec![]).is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 1); assert_transfer(&events[0], acc.alice, acc.bob, value); } @@ -162,7 +156,7 @@ macro_rules! tests { set_caller::(acc.bob); assert!(token.transfer(acc.charlie, 3 * value, vec![]).is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 3); assert_transfer(&events[0], acc.alice, acc.bob, value); assert_transfer(&events[1], acc.alice, acc.bob, 2 * value); @@ -178,7 +172,7 @@ macro_rules! tests { let start = recorded_events().count(); assert!(token.transfer(acc.bob, value, vec![]).is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0, "Transferring 0 tokens emitted event"); } @@ -223,7 +217,7 @@ macro_rules! tests { token.transfer(acc.bob, supply + 1, vec![]), Err(PSP22Error::InsufficientBalance) ); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0) } @@ -255,7 +249,7 @@ macro_rules! tests { assert_eq!(token.allowance(acc.alice, acc.bob), value); assert_eq!(token.allowance(acc.bob, acc.alice), 0); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 1); assert_approval(&events[0], acc.alice, acc.bob, value); @@ -276,7 +270,7 @@ macro_rules! tests { assert_eq!(token.allowance(acc.alice, acc.bob), value); assert_eq!(token.allowance(acc.bob, acc.alice), 0); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 1); assert_approval(&events[0], acc.alice, acc.bob, value); } @@ -302,7 +296,7 @@ macro_rules! tests { assert!(token.approve(acc.bob, 4 * value).is_ok()); assert_eq!(token.allowance(acc.alice, acc.bob), 4 * value); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 4); assert_approval(&events[0], acc.alice, acc.bob, value); assert_approval(&events[1], acc.alice, acc.charlie, 2 * value); @@ -321,7 +315,7 @@ macro_rules! tests { assert!(token.approve(acc.alice, value).is_ok()); assert_eq!(token.allowance(acc.alice, acc.alice), 0); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } @@ -336,7 +330,7 @@ macro_rules! tests { assert!(token.approve(acc.bob, value).is_ok()); assert!(token.increase_allowance(acc.bob, supply).is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 2); assert_approval(&events[0], acc.alice, acc.bob, value); assert_approval(&events[1], acc.alice, acc.bob, value + supply); @@ -357,7 +351,7 @@ macro_rules! tests { assert!(token.decrease_allowance(acc.bob, value).is_ok()); assert_eq!(token.allowance(acc.alice, acc.bob), 0); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 3); assert_approval(&events[0], acc.alice, acc.bob, 2 * value); assert_approval(&events[1], acc.alice, acc.bob, value); @@ -379,7 +373,7 @@ macro_rules! tests { Err(PSP22Error::InsufficientAllowance) ); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 1); assert_approval(&events[0], acc.alice, acc.bob, value); } @@ -397,7 +391,7 @@ macro_rules! tests { assert!(token.increase_allowance(acc.bob, 0).is_ok()); assert!(token.decrease_allowance(acc.bob, 0).is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 1); assert_approval(&events[0], acc.alice, acc.bob, value); } @@ -413,7 +407,7 @@ macro_rules! tests { assert!(token.increase_allowance(acc.alice, value).is_ok()); assert_eq!(token.allowance(acc.alice, acc.alice), 0); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } @@ -428,7 +422,7 @@ macro_rules! tests { assert!(token.decrease_allowance(acc.alice, value).is_ok()); assert_eq!(token.allowance(acc.alice, acc.alice), 0); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } @@ -489,9 +483,9 @@ macro_rules! tests { .transfer_from(acc.alice, acc.charlie, value, vec![]) .is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 2); - if let Event::Transfer(_) = events[0] { + if is_transfer(&events[0]) { assert_transfer(&events[0], acc.alice, acc.charlie, value); assert_approval(&events[1], acc.alice, acc.bob, value); } else { @@ -515,7 +509,7 @@ macro_rules! tests { Err(PSP22Error::InsufficientAllowance) ); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } @@ -540,7 +534,7 @@ macro_rules! tests { assert_eq!(token.balance_of(acc.bob), value); assert_eq!(token.allowance(acc.bob, acc.charlie), 2 * value); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } @@ -566,7 +560,7 @@ macro_rules! tests { assert_eq!(token.balance_of(acc.bob), value); assert_eq!(token.allowance(acc.bob, acc.charlie), value); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } @@ -586,7 +580,7 @@ macro_rules! tests { assert_eq!(token.balance_of(acc.alice), supply - value); assert_eq!(token.balance_of(acc.bob), value); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 1); assert_transfer(&events[0], acc.alice, acc.bob, value); } @@ -604,7 +598,7 @@ macro_rules! tests { .transfer_from(acc.alice, acc.charlie, 0, vec![]) .is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } @@ -621,7 +615,7 @@ macro_rules! tests { .transfer_from(acc.alice, acc.alice, 2 * supply, vec![]) .is_ok()); - let events = decode_events(start); + let events = get_events(start); assert_eq!(events.len(), 0); } } From fef7e07b731c1236ad8c13c688155bcbefbe4008 Mon Sep 17 00:00:00 2001 From: Michal Handzlik <4hansu@gmail.com> Date: Thu, 25 Jan 2024 16:35:23 +0100 Subject: [PATCH 3/5] New lean Cargo.toml --- Cargo.toml | 11 ++--------- errors.rs | 4 ++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a7cceb7..9d61b7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,20 +11,13 @@ description = "Minimal implementation of PSP22 token standard in pure ink!" exclude = [ ".github/*" ] [dependencies] -ink = { version = "4.3", default-features = false } - -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } -scale-info = { version = "2.9", default-features = false, features = ["derive"], optional = true } +ink = { version = "5.0.0-rc", default-features = false } [lib] path = "lib.rs" [features] default = ["std"] -std = [ - "ink/std", - "scale/std", - "scale-info/std", -] +std = ["ink/std"] contract = [] ink-as-dependency = [] diff --git a/errors.rs b/errors.rs index fcf22ae..7d91113 100644 --- a/errors.rs +++ b/errors.rs @@ -1,7 +1,7 @@ use ink::prelude::string::String; -#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] pub enum PSP22Error { /// Custom error type for implementation-based errors. Custom(String), From 3d988e9bf29585694e0098b82bb8625dda148937 Mon Sep 17 00:00:00 2001 From: Michal Handzlik <4hansu@gmail.com> Date: Fri, 26 Jan 2024 12:27:06 +0100 Subject: [PATCH 4/5] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9d61b7f..ec575e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "psp22" -version = "0.2.1" +version = "0.3.0" edition = "2021" authors = ["Cardinal"] homepage = "https://github.com/Cardinal-Cryptography/PSP22" From 2330b078b42853198b72271f3c02633c4ba17138 Mon Sep 17 00:00:00 2001 From: Michal Handzlik <4hansu@gmail.com> Date: Fri, 26 Jan 2024 13:16:21 +0100 Subject: [PATCH 5/5] Add comments --- events.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/events.rs b/events.rs index 03daaa0..3f125ff 100644 --- a/events.rs +++ b/events.rs @@ -1,19 +1,27 @@ use ink::primitives::AccountId; +/// Event emitted when allowance by `owner` to `spender` changes. #[ink::event] pub struct Approval { + /// Account providing allowance. #[ink(topic)] pub owner: AccountId, + /// Allowance beneficiary. #[ink(topic)] pub spender: AccountId, + /// New allowance amount. pub amount: u128, } +/// Event emitted when transfer of tokens occurs. #[ink::event] pub struct Transfer { + /// Transfer sender. `None` in case of minting new tokens. #[ink(topic)] pub from: Option, + /// Transfer recipient. `None` in case of burning tokens. #[ink(topic)] pub to: Option, + /// Amount of tokens transferred (or minted/burned). pub value: u128, }