Skip to content

Latest commit

 

History

History
218 lines (168 loc) · 8.21 KB

2007.md

File metadata and controls

218 lines (168 loc) · 8.21 KB

🏠 HOME

PSP34 and OpenBrush

What is PSP-34?

PSP34 is Polkadot’s native NFT standard, designed to address ERC721’s limitations while leveraging Polkadot’s strengths.

What is OpenBrush?

OpenBrush to Polkadot is what OpenZeppelin is to Ethereum. A library of secure, reusable smart contracts for Ink! (instead of Solidity). It provides:

  • Pre-built Standards: PSP22 (fungible tokens), PSP34 (NFTs), PSP37 (multi-tokens).
  • Extensions: Royalties, enumerable NFTs, batch transfers.
  • Security Audits: Community-vetted code to prevent vulnerabilities (no more reentrancy hacks!).

ERC721 vs PSP34

Feature ERC721 (Ethereum) PSP34 (Polkadot)
Language Solidity Rust (via ink!)
Security Runtime checks Compile-time guarantees (no overflows)
Cross-Chain Requires bridges (e.g., LayerZero) Native XCM support
Metadata Optional (tokenURI) Built-in (via PSP34Metadata extension)
Batch Ops Not natively supported transfer_batch (gas-efficient)

Why Polkadot didn’t adopt ERC721

  1. Architectural Mismatch:

    • ERC721 relies on EVM/Solidity, while Polkadot uses Wasm/Rust. Rewriting ERC721 for ink! would be inefficient.
  2. Cross-Chain Needs:

    • PSP34 natively supports XCM (cross-chain messaging), enabling NFTs to move between parachains without bridges.
  3. Enhanced Security:

    • Rust’s ownership model prevents common ERC721 pitfalls (e.g., reentrancy, integer overflows).
  4. Governance & Upgradability:

    • PSP34 integrates with Polkadot’s on-chain governance, allowing standards to evolve without hard forks.
  5. Scalability:

    • Batch operations (e.g., minting 100 NFTs in one TX) reduce gas costs — critical for parachains like Unique Network.

Cardano comparison with CIP25

Aspect Cardano (CIP25) Polkadot (PSP34)
Standard CIP25 (metadata-centric) PSP34 (behavior-centric)
Cross-Chain Limited (requires bridges) Native via XCM
Royalties Post-mint marketplace enforcement Enforced at contract level

Dealing with the OpenBrush implementation of PSP34

While the code demonstrates core NFT functionalities (mint, burn, transfer), deploying it to live networks is currently unfeasible due to OpenBrush’s outdated dependencies. We’ll focus on understanding the code and testing it locally.

lib.rs

#![cfg_attr(not(feature = "std"), no_std, no_main)]

// OpenBrush macros to auto-implement PSP34 traits and extensions
#[openbrush::implementation(PSP34, PSP34Burnable, PSP34Mintable)]
#[openbrush::contract]
pub mod psp34_ob {
    use openbrush::traits::Storage;

    // Storage structure for the NFT contract
    #[ink(storage)]
    #[derive(Default, Storage)]
    pub struct Contract {
        // OpenBrush's macro injects PSP34 storage logic (owners, token IDs)
        #[storage_field]
        psp34: psp34::Data,  // Holds NFT ownership and metadata
    }

    impl Contract {
        // Constructor: Mints NFT #1 to the deployer
        #[ink(constructor)]
        pub fn new() -> Self {
            let mut _instance = Self::default();
            // Internal helper to mint token ID 1 (type U8) to the caller
            psp34::Internal::_mint_to(
                &mut _instance,
                Self::env().caller(),
                Id::U8(1)  // ID format (U8, U16, U32, etc.)
            ).expect("Can mint");
            _instance
        }
    }
}

Cargo.toml

[package]
name = "psp34_ob"
version = "0.1.0"
authors = ["Your Name <[email protected]>"]
edition = "2021"

[dependencies]
ink = { version = "4.2.1", default-features = false }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }
openbrush = { version = "4.0.0-beta", default-features = false, features = ["psp34"] }

[lib]
name = "psp34_ob"
path = "lib.rs"

[features]
default = ["std"]
std = [
    "ink/std",
    "scale/std",
    "scale-info/std",
    "openbrush/std",
]

ink-as-dependency = [] 

Key Features Explained

  1. PSP34 Extensions:

    • PSP34Burnable: Allows burning NFTs.
    • PSP34Mintable: Enables minting new tokens (like ERC721’s _mint).
  2. Storage:

    • psp34::Data: Auto-injected by OpenBrush to track ownership (equivalent to ERC721’s mapping(uint256 => address)).
  3. ID Types:

    • Id::U8(1): Tokens can use integer IDs (U8, U16, etc.) or bytes.
  4. Tests:

    • Simulate blockchain interactions (minting, transfers) without a live network.

Why deployment fails

1. Outdated OpenBrush Dependencies

  • Ink! 3.x vs 4.x: OpenBrush relies on older ink! versions (3.x), while modern parachains require 5.x.

  • WASM Validation Errors:

    Error: This contract file is not in a valid format.

    Parachains like Astar reject contracts built with deprecated toolchains and even downgrading ink! doesn't help.

Testing Strategy

Since deployment is blocked, unit tests are your only validation tool:

    // Unit tests (the only reliable way to validate this contract)
    #[cfg(test)]
    mod tests {
        use super::*;
        use ink::env::test;

        // Test 1: Verify initial minting in constructor
        #[ink::test]
        fn test_new() {
            let contract = Contract::new();
            let caller = test::default_accounts::<ink::env::DefaultEnvironment>().alice;
            // Check if Alice (caller) has 1 NFT
            assert_eq!(PSP34::balance_of(&contract, caller), 1);
        }

        // Test 2: Mint a new NFT (ID #4)
        #[ink::test]
        fn test_mint() {
            let mut contract = Contract::new();
            let caller = test::default_accounts::<ink::env::DefaultEnvironment>().alice;
            // Mint token ID 4 to Alice
            psp34::Internal::_mint_to(&mut contract, caller, Id::U8(4)).expect("Can mint");
            // Alice now owns 2 NFTs (IDs 1 and 4)
            assert_eq!(PSP34::balance_of(&contract, caller), 2);
        }

        // Test 3: Burn NFT #1
        #[ink::test]
        fn test_burn() {
            let mut contract = Contract::new();
            let caller = test::default_accounts::<ink::env::DefaultEnvironment>().alice;
            // Burn token ID 1
            psp34::Internal::_burn_from(&mut contract, caller, Id::U8(1)).expect("Can burn");
            // Alice's balance drops to 0
            assert_eq!(PSP34::balance_of(&contract, caller), 0);
        }

        // Test 4: Transfer NFT #1 to Bob
        #[ink::test]
        fn test_transfer() {
            let mut contract = Contract::new();
            let accounts = test::default_accounts::<ink::env::DefaultEnvironment>();
            let alice = accounts.alice;
            let bob = accounts.bob;
            // Transfer token ID 1 from Alice to Bob
            PSP34::transfer(&mut contract, bob, Id::U8(1), b"".to_vec()).expect("Can transfer");
            // Verify balances
            assert_eq!(PSP34::balance_of(&contract, alice), 0);
            assert_eq!(PSP34::balance_of(&contract, bob), 1);
        }
    }

Key takeaways

  • OpenBrush is not mantained: Useful for learning, but avoid for production.
  • Stick to Tests: Validate logic locally until tooling stabilizes.
  • Ditch OpenBrush: Write the standard from scratch (if you cannot wait an updated version).

🏠 HOME