#smart-contracts #blockchain #ink #wasm

no-std pendzl

pendzl library for smart contract development on ink!

18 releases (1 stable)

1.0.2 Nov 5, 2024
1.0.1-v1calls Aug 5, 2024
0.2.4 Mar 26, 2024
0.2.4-v1calls3 Jun 3, 2024
0.1.0-alpha2 Jan 24, 2024

#176 in Magic Beans

Download history 59/week @ 2024-07-29 249/week @ 2024-08-05 122/week @ 2024-08-26 28/week @ 2024-09-16 20/week @ 2024-09-23 13/week @ 2024-09-30 1/week @ 2024-10-14 129/week @ 2024-11-04 5/week @ 2024-11-11

134 downloads per month
Used in 2 crates

MIT license

180KB
3.5K SLoC

Summary

Pendzl is a library for smart contract development on ink!.

Why use this library?

  • To make contracts interoperable to do safe cross-contract calls (by having the same function's signature among every contract)
  • To ensure the usage of Polkadot Standards Proposals
  • To ensure the usage of the latest & most secure implementation
  • Useful contracts that provide custom logic to be implemented in contracts
  • To save time by not writing boilerplate code
  • Useful features that can simplify development
  • All contracts are upgradeable by default

Which Standard tokens & useful contracts does it provide?

  • PSP22 - Fungible Token (ERC20 equivalent) with some extensions including Vault - a modified ERC-4626 contract!
  • PSP34 - Non-Fungible Token (ERC721 equivalent) with some extensions
  • (not yet supported) PSP37 - ERC1155 equivalent with extensions
  • Ownable Restrict access to action for non-owners
  • Access Control Define a set of roles and restrict access to action by roles
  • Pausable Pause/Unpause the contract to disable/enable some operations
  • (not yet supported) Timelock Controller Execute transactions with some delay
  • (not yet supported) Governor Govern
  • General Vester Allows for the creation of a vests

This library was created based on ideas of implementation macro, Storage trait, and storage_item macro that came from openbrush-contracts.

How does it work?

Library allows for reusing implementations of the supported traits for any ink! smart-contract. It's goal is to be modular and extensible without unnecessary complexity overhead.

Deriving implementations of provided traits is easy. For example, in case of a PSP22:

add a field of of type PSP22Storage and annotate it with #[storage_field] add StorageFieldGetter to storage struct's derive macro (with the above allows for access to the storage item via .data()) make your contract reuse the PSP22 implementation via #[pendzl::implementation(PSP22)] annotation on top of your contract! The first two steps are to satisfy required boundaries by default implementations - contract's storage must implement StorageFieldGetter with appropariate generic T (in this case it's PSP22Storage).

#[pendzl::implementation(PSP22)]
#[ink::contract]
pub mod my_psp22 {

    #[ink(storage)]
    #[derive(StorageFieldGetter)]
    pub struct Contract {
        #[storage_field]
        psp22: PSP22Data,
    }
}

The #[pendzl::implementation(PSP22)] macro implements the PSP22 by including the following code after the macro is expanded:

impl  pendzl::contracts::psp22::PSP22Internal for Contract {
            fn _total_supply(&self) -> Balance {
                pendzl::contracts::psp22::implementation::PSP22InternalDefaultImpl::_total_supply_default_impl(self)
            }
}

impl pendzl::contracts::psp22::PSP22 for Contract {
        #[ink(message)]
        fn total_supply(&self) -> Balance {
            pendzl::contracts::psp22::implementation::PSP22DefaultImpl::total_supply_default_impl(self)
        }
}

One can override the default_impl of functions from PSP22 and/or PSP22Internal as follows:

    const HATED_ACCOUNT: AccountId = <some_account>

    #[overrider(PSP22Internal)]
    fn _update(
        &mut self,
        from: Option<&AccountId>,
        to: Option<&AccountId>,
        amount: &Balance,
    )-> Result<(), PSP22Error> {
        /// one can use default_impl as in this example or provide completely new implementation.
        pendzl::contracts::psp22::implementation::PSP22InternalDefaultImpl::_update_default_impl(self, from, to, amount)
    }

    #[overrider(PSP22)]
    fn approve(
        &mut self,
        spender: AccountId,
        value: Balance,
    ) -> Result<(), PSP22Error> {
        if spender == HATED_ACCOUNT {
            return Err(PSP22Error::Custom(String::from("Hated account can not have allowance to spend tokens")));
        }
        /// one can use default_impl as in this example or provide completly new implementation.
        pendzl::contracts::psp22::implementation::PSP22DefaultImpl::approve(self, spender, value)
    }

the above overrider functions (marked with #[overrider(...)] ) will be consumed by the #[pendzl::implementation(PSP22)]. As result, the body of the functions will be used to implement the apropariate function in apropariate trait. As showed in above example, one can still use the default implementation if one only wants to add some logic before/after the default implementation or one can fully reimplement the function.

Similar logic applies to storage items. One may not want to use the default PSP22Data and use his PSP22CustomData. This can be achieved in two ways:

  • by implementing PSP22Storage trait for PSP22CustomData,
  • overriding all functions from PSP22Internal trait

Installation & Testing

To work with the project you need to install ink! toolchain and NodeJS's dependencies.

  1. you need an installer rustup.
  2. ink! toolchain
  3. NodeJS deps you can install via pnpm command inside tests/ folder

Build

To build exapmles use

$ bash build_examples.sh

Tests

If you want to run tests enter the tests/ foldr and run

$ pnpm i
$ pnpm build:debug
$ pnpm test

FAQ

Was it audited?

Contracts & lang folder in this repository have been audited by Kudelski Security. The audit report can be found here.

Findings that were not resolved have been addressed & explained here.

License

pendzl is released under the MIT License.

Dependencies

~8–12MB
~209K SLoC