Nips
Last updated
Last updated
NIPs (short for Nana improvement programs) refer to whitelisted programs, written in javascript (ECMAScript 2023), that create and expose view
and state_changing
methods.
NIPs have their own storage ( ) and Nana balance ( ), which UTXOs can transfer into or transfer out of. Both of these are manipulated through the Call cacheand committed at the end of a call.
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.
A method can only call state changing methods (even if the method is part of the same program) or view
methods from another program with the call()
macro. See . Furthermore, a method needs to
View
methods can take in whatever params they like, while state_changing
methods take in only one param which is an array representing the CallData payload constructed at the indexer level
NIPs dont have constructors. Instead, you can implement a constructor with a state_changing
method that is whitelisted to only owner
and can only be called once (with the logic enforced with a value in being set if the method has been called once, and a checking said value)
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:
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
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.
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.
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.