Seed - Development Design - Module, Smart Contracts & The SVM
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 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
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.
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.
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.
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 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 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.