HookPoints Pallet

License: Apache 2.0   Rust   Polkadot

The HookPoints pallet provides a flexible framework to interact with registered callbacks within the Substrate runtime. Users can register global callbacks and specific callbacks, offering a robust mechanism to invoke extensions from different developers or protocols.

Conceptual Overview

Hook points draw inspiration from the web2 era, where platforms like Magento, WordPress, and Shopify thrived due to their extensible plugin systems. Similarly, Substrate offers a specialized blockchain core that emphasizes the Unix principle of doing one thing right. Hook points build on this, introducing a plugin system that enhances core functionalities and allows for added innovations.

Using ink! and pallet_contracts, hook points enable seamless integration between Substrate pallets and ink modules. They abstract away challenges like byte handling and encoding, providing a higher-level interface for developers. This bridges the Substrate and ink environments, simplifying development.

To complement this, a CLI tool is available for hook points. It automates code generation for both Substrate and ink, handling tests and other essentials. This minimizes boilerplate, letting developers focus on core logic, and streamlining the blockchain development process.

Features

Prerequisites

Ensure that the pallet_contracts is included in your runtime, as HookPoints relies on it for contract interactions.

Install the CLI Tool:

```shell

cargo install hookpoints-cli ```

Installation

To add the HookPoints pallet to your runtime, add it as a dependency in your Cargo.toml and then include it in your runtime's list of pallets.

rust impl pallet_hookpoints::Config for Runtime { // your event of choice type RuntimeEvent = RuntimeEvent; // max length of a callback, this is typically ContractName::function_name type MaxLengthId = ConstU32<64>; // runtime weights, if in doubt use ours type WeightInfo = pallet_hookpoints::weights::SubstrateWeight<Runtime>; }

Usage

Move to your substrate root folder and run:

shell hookpoint-cli configure

This will guide you through the setup process of creating hook points. You give your project a name (this will uy be the name of the trait that ink! devs need to implement).

Afterwards you can interactively create hookpoints:

For example, at GenesisDao, we use hookpoints everywhere.

For an example, we want to have a hookpoint where DAO owners can alter the voting power of their token holders to implement alternative voting models, add vesting wallets and so on. So we have a hook point called on_vote, added the arguments voter as AccountId and voting_power as Balance. We return the changed voting_power as balance and per default just the unaltered voting_power.

The CLI will create a hookpoints.json in your root folder.

Run

shell hookpoint-cli generate

And the CLI will generate

Go to your pallet where you want to implement the hookpoint and just use the provided function in hooks.rs.

In our example this would be:

rust let new_voting_power = on_vote(dao_owner, original_caller_of_the_extrinsic, voter, voting_power);

That's it.

Extrinsics

Users can register a global callback, which becomes the default point of interaction for any calls unless a specific callback is defined for that particular interaction.

This is normally done by someone who "owns" a part of your application and wants to alter it's behaviour. In the example above it's DAO.

Registering a Global Callback

Users can also define a global callbacks which will be used as a default callback when no specific callback is defined.

rust assert_ok!(HookPoints::register_global_callback(origin, contract_address));

Registering a Specific Callback

Users can also define specific callbacks, which will take precedence over global callbacks when triggered.

rust assert_ok!(HookPoints::register_specific_callback( RuntimeOrigin::signed(ALICE).into(), contract_address, selector_id_as_stringd ));

The id can be found as label in the contract.json, when using hookpoint-cli (highly recommended) it's just ChoosenName:choosen_hookpoint_name.

Testing

Here's how a typical test might look like:

```rust

[test]

fn executecallback() { newtestext().executewith(|| { let creator = AccountId32::new([1u8; 32]);

    // your compiled wasm ink contract!
    let contract_path = "test_contract.wasm";

    let contract_deployment = HookPoints::prepare_deployment("new", creator.clone(), std::fs::read(contract_path).unwrap(), vec![])
        .add_init_arg(42u128);

    let contract_address = HookPoints::install(contract_deployment)
        .expect("Contract installation should be successful");

    // Register the contract for callbacks
    HookPoints::register_global_callback(RuntimeOrigin::signed(creator.clone()), contract_address.clone()).unwrap();

    // Create a HookPoint for the "multiply" function
    let hookpoint = HookPoints::create("multiply", creator.clone(), creator.clone())
        .add_arg(2u128);

    // Execute the "multiply" function using the HookPoint
    let result: Result<u128, _> = HookPoints::execute(hookpoint);

    // Ensure the result is Ok and equals to 84 (since 42 * 2 = 84)
    assert_eq!(result.unwrap(), 84);
});

} ```

This uses our test contract from the ./contract folder.

License

Licensed under the Apache License, Version 2.0. You may not use this pallet except in compliance with the License.