Creating your first custom Extension

Quick start guide to implementing, deploying and extending with your first Extension

Custom Extensions can be built through the inheritance of a single contract Extension.sol and defining your functional logic. State variables are managed through structs made accessible through Storage libraries.

The basics

First install the framework to make it available in your development environment:

npm install @violetprotocol/extendable

In addition to inheriting the core Extension.sol base, each extension contract must also implement a custom defined interface:

import "@violetprotocol/extendable/Extension.sol";
import "IYourExtension.sol";

contract YourExtension is IYourExtension, Extension {
    // define your implementation here
}

The Extension.sol base in an abstract contract that also requires you to implement two functions: getInterface() and getInterfaceId().

Here is a concrete example:

import "@violetprotocol/extendable/Extension.sol";

interface IYourExtension {
    function yourFunction() external pure;
}

contract YourExtension is IYourExtension, Extension {
    function yourFunction() override pure public {
        // your implementation here
    }
    
    function getInterface() override public pure returns(string memory) {
        return "function yourFunction() external pure;\n"
    }
    
    function getInterfaceId() override public pure returns(bytes4) {
        return(type(IYourExtension).interfaceId);
    }
}

Now you have your own custom Extension!

Observe that the result of getInterface() looks identical to the contents of your IYourExtension interface. The result of getInterfaceId() is also derived from your interface definition.

Your extension can now be deployed and with your deployed contract address you can extend your Extendable contract:

YourContract.extend(yourExtension.address)

If your custom functions require access to state variables, continue reading.

Developing further

Your extension can be as complex or simple as you desire. It might be cleaner to organise functionality into modules and deploy sets of functions as extensions or be as lean as possible by deploying individual functions as extensions for maximum flexibility.

Leverage the ExtendLogic functionality and inspect your Extendable by calling:

YourContract.getCurrentInterface() to return all the functions your contract now has access to.

Interacting with state

Once you've grasped the basics of creating an Extension, defining more complex functional logic is the next step. As with all code, there is always a need to read and write data, and with smart contracts this is normally handled with state variables declared and referenced in the same context. This changes with the use of Extendable as state is accessed on a modular basis by Extensions so we have to manage this differently.

We use Storage library contracts to encapsulate all the state we need for your Extension to carry out its function.

Take a look at our example Storage library template:

struct YourStruct {
    uint someUint;
}

library StorageTemplate {
    bytes32 constant private STORAGE_NAME = keccak256("your_unique_storage_identifier");

    function _getStorage()
        internal 
        view
        returns (YourStruct storage state) 
    {
        bytes32 position = keccak256(abi.encodePacked(address(this), STORAGE_NAME));
        assembly {
            state.slot := position
        }
    }
}

Define any state variables inside YourStruct and make sure to include your Storage library into YourExtension. To access your state variables for reading or writing, reference the Storage library in your extension in the following way:

import "@violetprotocol/extendable/Extension.sol";
import "YourStorage.sol";

contract YourExtension is IYourExtension, Extension {
    ...
    
    function yourFunction() override pure public {
        YourStruct storage state = YourStorage._getStorage();
        
        // read values from state variables
        uint value = state.someUint;
        
        // assign values to state
        state.someUint = 1;
        
        // access library functions
        YourStorage.yourLibraryFunction();
    }
}

Using the above, once you extend your Extendable contract with your custom Extension, it will read and write values to the contract state of your Extendable as normal, except it is encapsulated by a struct and exposed by the library's _getStorage() function.

Include all variables you need into the struct. The struct can be as complex or simple as required including nested structs, mappings, dynamic arrays etc.

Last updated