Seed - Development Design - Module, Smart Contracts & The SVM

in #blockchain3 years ago


With the intended goal of the Seed project to become an agnostic DApp development ecosystem, properly designing how these DApps are developed is crucial to the projects success. Today we're going to talk about the design Seed is going with for the Modules & Smart Contract development, as well as how the virtual machine will work.


The Seed project views DApps as miniature classes known as Modules. Modules can be considered to be Seed's take on Smart Contracts. These modules have their own key-value data storage on the ledger, as well as public functions users use to communicate with the DApp. Each DApp user has their own key-value storage, allowing modules to store both global module data and local user data when executing their functions.

A "Virtual Machine" will exist to manage these modules. In reality, this virtual machine is not a true "Virtual Machine", as it simply runs on top of JavaScript, and does not do its own interpreting of function execution. Instead, the virtual machine acts as a wrapper for the modules, managing how they are accessed and acting as an intermediary between the users, modules and the ledger.

A local virtual machine can be fed any function to simulate functions executing on the blockchain. Provable execution through function hashing will be used to prove all users are invoking the same versions of each functions, and validating that the same end results occur when modifying the ledger.

Seed Virtual Machine

As stated above, the Seed Virtual Machine (SVM) is not a true virtual machine. Instead of having its own language, interpreting OPCODES or JavaScript on its own, it will instead leave the interpretation of code as an implementation detail, relying on the browser itself to interpret how these functions execute. JavaScript may evolve and may perform differently on differing browsers, however provable execution will ensure that users agree on what the interpretation of JavaScript did to the ledger. This means that, despite users potentially running different versions of JavaScript on differing browsers, updates are validated by other users, enforcing a "majority rules" mentality to which JavaScript version and browser interpreter becomes the standard. As long as differing browsers and JavaScript versions agree on how the simple instructions execute, they will continue to be approved. For more information on why JavaScript was chosen, please read our article on Custom Languages And Virtual Machines.

Instead of interpreting JavaScript, the SVM's job is to manage modules, executing functions, passing the appropriate parameters to functions, and acting as an intermediary between the ledger and modules.

Provable Execution

The first technical hurdle that the SVM must overcome is implementing provable execution. Provable execution is the act of proving that a user is being honest with their claims. Prove they are using the same version of a module as us, the exact same functions, and proving we agreed on how these functions modified the ledger. We wrote an article on Provable Execution With Function Hashing In JavaScript which goes into detail on the matter here.

Managing Functions

Due to functions being executed by various JavaScript versions and browsers, functions within modules must be controlled and simple. Only vanilla JavaScript features will be permitted, as well as the scope of a function cannot leave its method. That is, functions within modules will not be permitted to access external JavaScript code outside of the function itself.

Despite these constraints, functions still need to be able to access data from the ledger, access other modules, and state what changes to make in the ledger, among other features. Balancing these rules will be maintained through a form of dependency injection. More specifically, functions will be given a read-only and write-only parameters that give them access to permitted functionality outside of the function itself.

This makes it very easy for the virtual machine to manage the functions, giving them the required functionality, while also allowing us to keep the functions themselves simple, easily constraining them.


The Container is the read-only parameter given to all functions. Similar to dependency injection, a container acts as a window into other objects, allowing any object which has a container to request external function execution. The container will allow functions to read their modules data, read their users' data, invoke external module functions, as well as access a number of other functions.

Read Module Data

A container, first and foremost, allows any function to access its module or users data. Allowing functions to read state data is a simple requirement, however crucial for DApp development.

Cross-Module Communication

Through the container, functions can communicate with other modules. Any module can read the data of any other module. A module will be able to give permission to certain other modules to have write-access, however, as stated above, all modules will have read-access to all other modules.


The container will easily allow a function to know who owns this function execution. This is effectively the same as the


value in Ethereum's solidity, letting functions know on whose behalf they are acting on.


True randomness is nearly impossible in the world of computing. In the world of blockchain development, it is outright impossible. Instead, a deterministic pseudo-randomness function will be required for many DApps.

The main concerns with pseudo-randomness are having it give a truly fair distribution of numbers (for example, the odds of getting a 1 should be the same as a 6), as well as guaranteeing a lack of predictability for generated numbers. When dealing with the predictability, there are two additional concerns we must evaluate. First, when a user invokes the pseudo-random function, they should not be able to easily determine in an abusable way what the results will be of this function. Secondly, and more importantly, a user should not in any way be able to change, force or control the generation of the random number.

To explain the difference between the two, consider this. Knowing a dice roll will be a six is a flaw, as knowing the result allows them to choose whether or not to even attempt the gamble. However, letting users control whether that dice roll becomes a two or a six is a critical, game breaking issue.

The container will implement pseudo-randomness, which will require deterministically generating a seed through variables that the invoking user does not have control over, and then passing that seed into a "randomize" function which creates a fair distribution of numbers. The seed could, for example, be derived through a variation of previous transaction hashes, or even through a separate module which can gain entropy through other external users.


ChangeContext's are the second parameter passed into functions when executing code in the SVM. A ChangeContext is a write-only object used to make changes to a modules state data or a users' data.

This object would wrap all changes to make them fit easily into the ledgers data schema without requiring the developer to have to know the underlying schema itself.

Any function which chooses to not include a ChangeContext in the parameters is considered a getter function, while functions that include the ChangeContext are considered state-modifying functions.


Modules are the "Smart Contracts" of Seed. They offer trusted state data for their module and for users of a module. Modules are essentially a mapping of key-value pairs for data storage, and an array of functions.

Local Data

Modules contain local data pertaining to the module itself in the form of key-value storage. Storage can be either a number, a string or an object.

User Data

Modules also contain user data for every user which uses the module. This user data structure is defined at module creation time, with a copy of the data schema being added on behalf of each user upon the first time that the user is referenced within the module. Therefore, the first time a user uses a module, or the first time another user references a given user within a function, will result in the new user being given a blank set of data for the module.

The user data is actually simply a mapping inside the local data called "userData". For this reason, "userData" is a protected term that cannot be used within functions.


Each module has one or more functions associated with it. These functions give logic to the module and are all public, invokable by external users and other functions. Functions require a container parameter in order to read module data, however they can also require a ChangeContext parameter if they intend on modifying a modules state.

State-Modifying Functions

State-Modifying functions require a ChangeContext parameter. This parameter allows them to describe to the ChangeContext about what module and user data state are being modified and how.

Getter Functions

Getter functions are functions that do not modify the state and simply omit the ChangeContext parameter. These functions simply read the state data and return whatever information they choose. They can call other getters and contain as much logic as they please, however they cannot modify the ledgers state.


To listen to the audio version of this article click on the play image.

Brought to you by @tts. If you find it useful please consider upvoting this reply.