Nips

NIPs (short for Nana improvement programs) refer to whitelisted programs, written in javascript (ECMAScript 2023), that create and expose view and state_changing methods.

A NIP is created by creating a function that returns other functions (the view and state_changing methods) and an optional disable function. Methods of type view must have their functions prefixed with $ while state_changing methods don't. New NIPs should implement method names with snake case.

Any method type can call other view methods in the same program with the function name.

NIPs can include stuff other than methods within the function, but it should be noted that anything done here is local to a call. Example if a const is set here, the const is available to all methods but is not part of the Nana state . Changing a variable set in this way does not alter the Nana state in any way. You can use this logic to destructure comonly used macros ( Macros specification ) from the Nana object passed that is passed to the parent of every NIP function.

A NIP returns object with four fields "views", "state", "disable" and "use".

  • Views and state are objects containing all the view (views field) and state_changing (state) methods mapped by their ids (a u8 representing a unique identifier for the method, shared between views and state_changing methods ).

  • The "disable" field is a special method called in the scenario a disable_tx is ever defined (see Disable_tx ). It does not have an ID, cannot be called with call() , however is a state_changing method and bound to the rules of such.

  • The "use" field is an array of ProgramId 's a program could potentially call or alloc() to.

Example nip:

const example_nip = (Nana) => {
    /*
        This nip creates a value "example_value" inside the programs storage
        inside a constructor. Then anyone can increment this value with
        increase_value or get it with $get_value.
    */
    
    const {
        $assert,
        $get,
        alloc,
        set
    } = Nana;
    
    const $constructor_initiated = ({...params}) => 
        get("constructor").exists && get("example_value").exists
    
    const $get_value = (params) => {
        
        $assert($constructor_initiated())
    
        return get("example_value").value
    }
    
    const disable = () => {
        /*
            0:2 is the id for disable_fallback - an emergency multi-sig NIP
            that recives Nanas on behalf of the protocol so they arent burnt
            and can be allocated to an upgrade of the program (in the form of
            a new nip) once the vulnerbility is fixed
        */
        
        //1 calls program
        call("0:2", 7)
        return true;
    }
    
    const constructor = (params) => {
        
        $assert(!$constructor_initiated())
        
        set("example_value", 7)    
        set("constructor", 1)
        return true;        

    }

    const increase_value = (params) => {
  
        $assert($constructor_initiated())
        
        set("example_value", $get_value() + 1)
        return true
        
    }
    
    return {
        views: {
            1: $get_value,
            2: $constructor_initiated
        },
        state: {
            0: constructor,
            3: increase_value
        },
        disable,
        use: ["0:2"]
    }
}

Versioning

Because of their nature of being a script embedded into an indexer, any time a new NIP is approved an added to the protocol indexers a fork will be created between the indexers that havent upgraded and the indexers that have.

To fix this nanas will only push official updates sparringly, an update which can contain many new nips added to an indexer. This means that a hardfork of Nanas is created every month that is incompatible with the previous' month's indexer - and the official Nana state considered is the one that is built by the latest version of Nana

Disable_tx

Nips are dangerous in the sense that once they are added to the protocol and included in a hardfork, theres no turning back. If there is an unknown vulneribility in the NIP that causes an indexer to exit or hang - this will break all indexers that upgrade.

A lot of due dillegence has to go into approving NIPs to be added to the Nanas protocol as they have the potential of breaking indexers - but even this is not full proof.

To solve this, all indexers keep track of a blacklist. A blacklist is a map of disabled NIPs, including a pointer to the block and tx they were disabled at (known as the disable_tx). NIPs can only have one disable_tx, after which the nip is disabled indefinitely.

disable_tx is an emergency measure that is only enforced if indexers break. An update is pushed with the new disable_tx disabling the vulnerable nip. Indexers must upgrade since if they don't they will be stuck at the vulnerable transaction (since the transaction that caused the indexer to hang or crash breaks their indexer). All previous transactions before disable_tx are still valid and the NIP is used, and after disable_tx the nip is disabled forever. Indexers will run a NIPs disable function at the moment of disable_tx , which can optionally transfer all Nanas from the program to somwhere else (recommended is (NIP2) treasury).

The disable function takes in no parameters.

If the disable function produces a rotten banana, all Nanas present in the program are burnt at the moment is disabled.

Furthermore any Nanas not trasnferred out by the disable function will be burnt.

The reason this is done, rather than just pushing a fix to the NIP, is because NIPs are meant to be immutable just like smart contracts. If a NIP can be changed to whatever people decide (including NanaLabs), their nature of being something immutable is lost.

TLDR: ANY CHANGES TO A NIP AFTER ITS INITIAL DEPLOYMENT TO THE NANA INDEXER IS CONSIDERED NON-STANDARD. NODES SHOULD NOT UPDATE TO AN INDEXER VERSION THAT CHANGES A NIP AND SUCH "UPDATES" SHOULD BE CONSIDERED AS MALICIOUS, EVEN IF THE UPGRADE COMES FROM NANALABS.

A methods return value

All state_changing methods in a NIP should return a boolean.

If the boolean is false a rotten banana is produced and the state cache is dropped, effectively reverting all changes made in the call.

If the boolean is true changes are committed to the Nana state

Methods that don't return anything will also produce rotten bananas (and in general is invalid and NIPs that could potentially do this will not be accepted)

See Call cachefor an overview of how this caching system should work when making an implementation of the Nana indexer.

Call cache

Calls need to commit changes to the Nanas state in a temporary cache which is commited the end of a state_changing method returning a truthy value. Because a Nana call could error (whether through a macro, an assert, etc) and produce a rotten banana.

-> The functionality for rotten bananas within the context of calls is that they should revert any state changes made to the global Nana state, and not process any further edicts with position=1.

To implement this any implementation of the Nana indexer must create a middleware library whose purpose is the following:

  • Programs commit state changes to the cache

  • Whenever a program wants to fetch something from the Nana state (whether it'd be a program balance, a programs storage, etc) the cache will first be checked. If the resource isn't found in the cache the fallback is whatever is on the Nana state. This ensures that a program's macros are self-aware of the changes its made in the call when fetching - even if it hasn't been committed to the Nana state yet.

  • The lib should have a commit function which upserts everything from the call cache to the Nana state if the return value of a state_changing method is truthy.

Last updated