Programs
Last updated
Last updated
Nana introduces the concept of "programs" to the original Runes protocol.
Every program has its own unique Nana balance, just like UTXOs. A program can be defined in one sentence: A programmable script with an administrative ability manipulate the unallocated Nana payload in a Banana process, in which they can allocate Nanas to themselves, to the payload, or to another programs balance.
Essentially, programmable nanas
The idea behind programs starts with the realization that the very nature of an indexer allows a programmable layer to be built into the processing of a banana; in which additional functionality can be introduced. If you have a good think about it, a lot of the rules and functionality built into the original runes protocol are all very arbitrary. Mints, Etchings , Edicts that transfer to every input by simply setting the amount to the amount of outputs present in a transaction. The philosophy of Nanas is rather than bake in more and more features into the protocol and overcomplicate the core of the protocol, we do something different..
Meet the Call
field. The call field, as the name suggests, calls an arbitrary script (or program) in the Nana ecosystem, which has the ability to add whatever arbitrary functionality they wish to the protocol through the manipulation of the unallocated Nana payload.
The functionality of programs starts with "the unallocated nana payload" , but what does that actually mean? Well, just like runes - an unallocated balance is created during the processing of a Banana, which edicts transfer out from. The functionality of this can be seen below:
Essentially, nana programs are fed the nanas from etchings, mints and edicts (with position=0
) which they can do whatever they want with. After the program finishes running, a new unallocated nana payload is created, which edicts at the end of a Banana process (position=1 )
can transfer out of.
An example use case could be something like a swap program ( (NIP1) bananaswap) where you mint an imaginary nana called Dog
, swap it for another Nana (could be our imaginary NBTC
nana mentioned in Concepts) and then transfer the NBTC
out of the Banana with an edict with position=1
, which would process after the swap. You essentially did 3 nana operations (mint, call (which did the swap) and transfer) in one transaction.
Programs are essentially like smart contracts, they expose methods that add specific functionality to a program. There are two types of methods in nanas: "view
" and "state_changing
"
"state_changing
" methods change the Nana state, and can only be called through the execution of a Banana.
A deep insight into this functionality can be found here: Nips
The "call" parameter can decide what state_changing
method to call by passing its method id (a u8).
Executable Calls that call a view
method are skipped immediately, as these have no change to the Nana state.
Every program on Nana has its own balance, and a vector of "boxes", each with its own balance.
A box is a balance group, controlled by the program, representing a specific subset of the programs entire balance.
The programs available balance is its main balance plus the aggregated Nanas of all boxes it owns.
Example:
Banana calls program A
Program A calls program B
Program B can alloc() to itself any nanas from banana's unallocated buffer or from program A's balance (or any box owned by Program A)
This allows for complex transfer logic to exist on Nanas, potentially bringing DeFi to the protocol, where programs can coexist and transfer nanas between each other.
Along with their Balance, nana programs also have their own storage, represented as a JSON blob.
A protocol being "stateful" refers to the concept that anyone can run the entire history of all call()'s on Nanas and get the same Nana state back, that is every single Balance, UTXO, storage blob, etc has to be the same and agreed upon by all Nana indexers.
This is because ANY VARIABILITY pinetiivably produces disagreements about the Nana state between indexers, and creates two completely different transfer and call history. Because of this, programs must not be able to add any variability into their methods. Example of this could be a random function that uses UNIX time as a seed (such as Math.random()) where UNIX time might be different on indexers depending on when they build the program.
Another example of variability could be Programs making inferences on whether they should drop a hanging call. Example lets say a method takes 200ms to run on one indexer, and 300ms on another. If there is a rule used by indexers to error a transaction if the transaction takes any longer than 250ms. Indexer 1 will consider the transaction valid and indexer 2 will not, and just like that an indexer disagreement is created. There's two ways to solve this issue:
The Ethereum virtual machine (EVM) solves this with the concept of "gas", where a solidity method is broken down to assembly instructions - and a gas tracker is kept based on the runtime complexity of a call. The higher the complexity, the higher the gas and the more the transaction cost.
This has two main problems specifically with Nanas. The first is that nanas is not a blockchain within itself, it doesn't have access to BTC transfers. We cant charge based on complexity because this functionality is available to the protocol.
Building a compiler and a language from the ground up that compiles down to assembly instructions for tracking runtime complexity is hard lol. Macros in Macros specificationwould need to have their own assembly instruction that communicates directly with the indexer, and then macros would have a gas price associated just like on the ethereum virtual machine.
Creating our own compiler also adds thousands of potential scenarios and edge cases where an indexer could break. However, with all that said - this is how programs should be implemented. A runtime complexity tracker that caps a transaction at x complexity at the assembly level would fix the hanging problem described, and allow indexers to un-anonymously agree on which transactions to drop or keep. People would inscribe bytecode on Bitcoin, indexers would fetch and compile the bytecode and then run each assembly instruction line by line, capping and dropping the transaction if x complexity is reached.
Nanas will allow for programs like these to exist on the protocol in the future - but such implementation should not be rushed and be developed with much care.
The other solution (and the intial solution the protocol will use) is to add new programs directly to the indexers source code - with only programs that we can verify won't break indexers or cause invariability issues being added. Anyone can create a whitelisted program, named Nana Improvement Programs ( Nips ) by creating PRs on the main Nana repo: https://github.com/runeapespad/Nana These will be reviewed to check that:
The NIP has the protocols community best interest in mind (we won't approve rug nips :P )
The NIP has Bitcoins best interest in mind (example: that the nip is anti-mev)
The NIP does not cause invariability issues between indexers and is stateful
The NIP follows the design structure specified through all pages in this documentation.
"view
" methods are read-only and do not change the Nana state, that is, a programs balance or its storage (see ).
Methods can transfer in and out of a programs balance or a box owned by the program with the macro). Relevant types can be seen below:"
Furthermore, child programs called from a main program with ( ) inherit the ability to ) any balance from parents higher up the call tree to themselves.
Methods can contribute to and manipulate a programs storage through the macro.
Methods can retrieve data from the programs storage with the macro.
Program storage is stored on the Nana state (essentially indexers). The nana state can be reverted incase a method call is invalid - in which any set()'s are reverted as well. Implementation of this can be seen here
A nip that passes all these checks will be added to the protocol in a future upgrade (see )
If a NIP has a vulnerability that causes invariability and this is discovered down the road, a disable_tx
event takes place, which is explained in detail here: