# Developer's Guide
The Miyabi smart contracts enable developers to use the powerful out-of-the-box provided features and use their own custom logic to create applications which run directly on the blockchain. This guide aims at enabling the smart contract developers to:
- Understand the basic pre-requisite knowledge required
- Where and how to begin
- Write and test a sample smart contract
- Learn about using smart contracts for more complex application development
# Pre-requisites
The Miyabi smart contracts are written in C# language. Currently, the following versions of the C# language are supported for writing the smart contracts:
- CSharp8
- CSharp9
- CSharp10
Following are few pre-requisite knowledge requirements:
C# Programming: A solid understanding of the C# programming language is necessary as it serves as the foundation for coding smart contracts. If you know generally any strongly typed OOP language like Java, C++ etc., it is fairly easy to get hold of C# very quickly.
Object-Oriented Programming (OOP) concepts: Familiarity with object-oriented programming concepts, such as classes, objects, inheritance, and polymorphism, is essential for building smart contracts in C#.
Blockchain fundamentals: Acquiring knowledge about the basics of blockchain technology, including its decentralized nature, consensus mechanisms, and transaction validation, will help in grasping the context in which smart contracts operate.
Miyabi fundamentals: Acquiring knowledge about basic Miyabi architecture and terminologies (like World State,
Transaction
etc.)
# Where and how to begin
There are plenty of online resources for learning C# and OOP. If the developer has minimal to no knowledge about what is a blockchain and how it works, it is recommended to have a quick look online.
If the developer has some basic clarity about blockchains, then the best way to get started is the General architecture of Miyabi itself
# Write a Smart Contract
# Smart Contract Lifecycle
In a compiled language, like Java, C# etc., normally code as text is first compiled to generate the binaries and then those binaries are used to run the application in a process hosted on a machine.
Similar to the above, the Miyabi smart contract can go through following basic phases:
- Contract as text: The source code for the smart contract must be written in and comply with the supported C# version.
- Compile the contract: Optionally, Miyabi contracts can be locally compiled to check whether there are any compile time errors.
- Deploy the contract to the blockchain: Using the
ContractDeploy
transaction entry, the developers can deploy their compilable code to the Miyabi blockchain. If successful, the code will be compiled using Miyabi rules and the binaries will be deployed in the blockchain. The smart contract files can be only deployed one time. An assembly id uniquely identifies a smart contract. Only the addresses which have theDeploy
contract permission can perform this operation. Also, using this transaction, the contract instantiator addresses can be set. These addresses have permission to create new contract instances. - Create instance of the contract: Using the
ContractInstantiate
transaction entry, a unique contract instance can be created. This smart contract instance is like a program running on a computer (blockchain). The user can perform read and write operations to the World State by calling methods targeting these instances of smart contracts using transactions. In theContractInstantiate
transaction entry, the user can also specify the addresses which own the particular instance of the contract ('InstanceOwner'). The contract instance owners have the permission to delete the contract instance. - Delete Contract instance: Using the
ContractInstanceDelete
transaction entry, an existing contract instance can be deleted. It is similar to gracefully killing an application running on a computer (blockchain).
It is recommended to go through a quick introduction to smart contracts. It gives a quick overview about the lifecycle of a smart contract and how to use miyabi-cli to do various operations.
# ContractBase Class
All the smart contracts in Miyabi must inherit from the ContractBase
abstract class.
This class has various properties and methods, which are crucial to understand before we start writing our own smart contract.
# Properties
InstanceName
: Name of the contract instance. For a given deployed contract assembly, it is a unique identifier for the contract instance.InstanceId
: It represents a unique internal identifier for the contract instance. It is derived by combining the assembly id, contract name and contract instance name.InternalTableName
: Represents the name of an internal table created for a contract instance. This table can be used by the contract developers to set and get data for its own purpose. It is persisted in the World State. This table is created when the contract instance is created, and it is deleted when the contract instance is deleted.Timestamp
: Can be used to get the current block timestamp at the time of execution of the transaction involving the contract.Height
: Represents the block height for the current block which contains the transaction involving the contract.ParentBlockId
: Represents the block id of the previous block.StateWriter
: An object which exposes the read and write operations to the World State. TheStateWriter
can be used to fetch the table readers and table writers for the specific tables in Miyabi World State. It can also be used to add or delete a table. When the contract is queried, theStateWriter
is marked read-only. When the contract is invoked, it is marked as writable.ExecutionContext
: Represents a runtime context which makes available the underlying Miyabi execution engine. It also exposes capability to communicate with other smart contract instances and call the invokable (state mutation possible) and queryable (state reading possible) methods.InternalTable
: The read restricted binary table that can only be read through this contract. Depending on whether theStateWriter
is read only or not, this property returns either a table reader or a table writer to the internal table.
# Methods
Generally, the methods of smart contract can be categorized as follows:
- Invokable methods: These methods can be used to make changes to the World State.
ContractInvoke
transaction entry can be used to call these methods. - Queryable methods: These methods can be used to query the World State or internal smart contract information. Apart from those, there are also two important methods:
Initialize
: This method is called when theContractInstantiate
transaction entry is executed. The default implementation just returnstrue
(expected output). If the method returnsfalse
or otherwise raises an error, the transaction entry execution is unsuccessful and hence the contract instantiation fails.
public virtual bool Instantiate(string[] args)
{
return true;
}
The developers can override this method and use it as a place to write startup logic for their application. For example, it can be used to create user defined tables or store some initialization related data or configuration.
Delete
: This method is called when theContractInstanceDelete
transaction entry is executed. The default implementation for this method returnstrue
(expected output). If the method returnsfalse
or otherwise raises an error, the transaction entry execution is unsuccessful and hence the contract instance deletion fails.
public virtual bool Delete()
{
return true;
}
The developers can override this method and use it as a place to write cleanup logic for their application. For example, it can be used to delete user defined tables or delete some other app data which was exclusive to the contract instance.
Apart from the above two methods which are crucial when creating or deleting the contract instance, there are other methods which serve different purposes during the active lifetime of the contract instance. These methods can be used by application developers to design the application logic. They are listed below:
SetInternalValue
: This method can be used to insert or update data (key and value) to theInternalTable
.TryGetInternalValue
: This method can be used to get data from theInternalTable
.GetInstanceOwners
: This method can be used to get the current owner addresses of the contract instance. This method is public and can be queried using API or miyabi-cli.UpdateInstanceOwners
: This method is responsible for updating the contract instance owners. The default implementation handles the graceful update of internal Miyabi tables which stores the instance owners for the particular contract instance. This method can be overridden by the user to add any other business logic on top of default implementation. In case the method is overridden by the developer, it is recommended to never skip calling the default implementation.InvokeExternalContract
: This method can be used to invoke another smart contract's method. Invoking another contract may involve presenting corresponding credentials. So, when calling this method, all such credentials should be presented.QueryExternalContract
: This method can be used to query another smart contract's method. Querying another contract may involve presenting corresponding credentials. So, when calling this method, all such credentials should be presented.
With understanding about the ContractBase
base class, it is easy to kick-start writing one's own smart contract.
For the course of this tutorial, we will refer this smart contract: SampleContract.cs (opens new window).
# Understanding SampleContract.cs
The sample smart contract explains how to create a fungible digital token application using Miyabi's Asset module.
The following operations are supported:
- Register an account address
- Delete an account address
- Check if an account is registered
- Generate the digital fungible token for an account
- Transfer the digital fungible token among accounts
- Get the current balance of a registered account
- Get the name of Asset table which is managing the digital fungible tokens
# Type Imports
The sample contract imports the following Miyabi types:
using Miyabi.Asset.Models;
using Miyabi.Common.Models;
using Miyabi.Contract.Models;
using Miyabi.ContractSdk;
Miyabi maintains a whitelist of allowed assemblies. Only the types in these assemblies can be used in the smart contract. The default allowed assemblies include Miyabi types, set of Microsoft libraries and some external libraries.
Similar to the whitelist, Miyabi also maintains a collection of forbidden members. These members constitute the libraries and types which are forbidden to be used in the smart contracts.
# How To Know Which Assemblies Are Allowed?
The Miyabi API and the Miyabi CLI can be used to get the information. The following API: /Contract/analyzer/parameters can be used to get the information about allowed and forbidden members. Alternatively, the getcontractanalyzerparameters CLI command can also be used to get the same information.
# What If The Type I Need Is Forbidden?
The ContractAnalyzerUpdate
transaction entry can be used to update the whitelist and forbidden list of members.
Updating this information is an admin level operation and requires signature of the address with WorldPermission.ChangeConfig
permission.
# Constructor
public SampleContract(ContractInitializationContext ctx)
: base(ctx)
{
}
The constructor simply calls the base constructor and does nothing more.
In fact, this is the recommended way in Miyabi smart contracts.
If there is some initialization code that is required at the time of creation of a new contract instance, it should go in the Instantiate
method.
# Instantiate
Method
public override bool Instantiate(string[] args)
{
// Do not accept any arguments
if (args.Length >= 1)
{
return false;
}
// Make the contract address as the table owner of the Asset table
// Contract address will also be the token admin for the Asset table
var tableOwnerAddresses =
new[] { ContractAddress.FromInstanceId(InstanceId) };
// Define the table descriptor for the Asset table
var assetTableDescriptor = new AssetTableDescriptor(
GetAssetTableName(),
false,
false,
tableOwnerAddresses,
false,
PermissionModel.TableAndRow);
// Try to create the unique Asset table for the contract instance
try
{
StateWriter.AddTable(assetTableDescriptor);
return true;
}
catch
{
return false;
}
}
The Instantiate
method is called when a new contract instance is created.
The contract instance is created if the method returns true
.
The developers can pass any number of arguments to this method.
In the SampleContract
, the arguments are not required, so an explicit check is added to make sure no argument is accepted.
During the instantiation of the SampleContract
, we want to:
- Create a Permissioned Asset table to store the digital tokens
- Return
false
in case table is not created
Each smart contract instance in Miyabi has its own unique address, and it can be used to perform permissioned operations. It is referred to as the contract address. For the table owner address, the contract address is used. Whenever any contract instance is invoked or queried, the contract address validates any credential requirement for its own address. It's also worth mentioning that the contract address cannot be used outside the contract. Contract address exclusively works when the contract is executing.
The Token admin for this table is the same as the Table Owner.
To create the table, the following needs to be done:
- Define the table descriptor.
It defines various metadata about the table.
- Name of the table
- Type of the table
- Table ACL
- Other flags and settings related to table
- Use the
StateWriter
to make a call to add the table to the World State.
# Delete
Method
public override bool Delete()
{
// Delete the Asset table that was created
// at the contract instantiation time
try
{
StateWriter.DeleteTable(GetAssetTableName());
return true;
}
catch
{
return false;
}
}
The Delete
method is called when deleting the contact instance.
It is a good place to delete any user defined data which is specific to the contract instance.
In SampleContract.Instantiate
method, a custom table was created.
So, in SampleContract.Delete
method, this table will be deleted.
The contract instance deletion will fail if this method returns false
.
To delete the table:
- Call the
StateWriter.DeleteTable
method - Return
false
if table deletion fails, otherwise returntrue
Other methods in this class are related to the logic for handling the digital token (the specific use case for this smart contract).
These methods generally need a table writer or a table reader.
Both are provided by the StateWriter
.
# RegisterAccount
Method
public void RegisterAccount(Address toRegister)
{
string tableName = GetAssetTableName();
ExecutionContext.StateWriter
.TryGetTableWriter<IPermissionedAssetTableWriter>(
tableName,
out var permissionedAssetTableWriter);
permissionedAssetTableWriter.RegisterAccount(toRegister);
}
This method is an invokable method and makes changes to the Asset table which stores the digital tokens.
It is responsible for:
- Registering an address in the permissioned asset table with zero balance.
No extra signatures are required as the Contract Address is the Table Owner.
# DeleteAccount
Method
public void DeleteAccount(Address toDelete)
{
string tableName = GetAssetTableName();
ExecutionContext.StateWriter
.TryGetTableWriter<IPermissionedAssetTableWriter>(
tableName,
out var permissionedAssetTableWriter);
permissionedAssetTableWriter.DeleteAccount(toDelete);
}
This method is an invokable method and makes changes to the Asset table which stores the digital tokens.
It is responsible for:
- Deleting a registered account
The account must have a zero balance in order to be deleted from the Asset table.
It requires signature of the row owner, that is, the toDelete
address.
The table level permissions are provided by the Contract Address.
# GenerateAssetToken
Method
public void GenerateAssetToken(Address toAddress, decimal amount)
{
string tableName = GetAssetTableName();
ExecutionContext.StateWriter.TryGetTableWriter<IPermissionedAssetTableWriter>(
tableName,
out var permissionedAssetTableWriter);
permissionedAssetTableWriter.MintTokens(toAddress, amount);
}
This method is an invokable method and makes changes to the Asset table which stores the digital tokens.
It is responsible for:
- Generating digital token assets for a specified address
- Registering the address if not yet registered
The StateWriter
can be used to get the table writer to the Asset table.
The Asset table has a pre-defined method MintTokens
to mint new tokens and register the unregistered addresses.
The permission required for this operation is that of a Token Admin and a Table Owner.
Since both these permissions are with the Contract Admin, there is no need for any extra signatures while invoking this smart contract method.
# MoveAssetToken
Method
public void MoveAssetToken(
Address fromAddress,
Address toAddress,
decimal amount)
{
string tableName = GetAssetTableName();
ExecutionContext.StateWriter
.TryGetTableWriter<IPermissionedAssetTableWriter>(
tableName,
out var permissionedAssetTableWriter);
permissionedAssetTableWriter.TransferTokens(
fromAddress,
toAddress,
amount);
}
This method is an invokable method and makes changes to the Asset table which stores the digital tokens.
It is responsible for:
- Transferring token from one address to another address
The operation requires permission of the fromAddress
address.
The Asset table has a pre-defined method called TransferTokens
for this operation.
# GetBalanceOf
Method
public decimal GetBalanceOf(Address accountAddress)
{
string tableName = GetAssetTableName();
ExecutionContext.StateWriter
.TryGetTableReader<IPermissionedAssetTableWriter>(
tableName,
out var permissionedAssetTableWriter);
permissionedAssetTableWriter.TryGetBalance(
accountAddress,
out var balance);
return balance;
}
This method is a queryable method and reads the data from the Asset table.
It is responsible for:
- Getting the balance of a registered address
Since the Asset table is not read restricted, there is no need for any signatures for calling this method.
# IsAccountRegistered
Method
public bool IsAccountRegistered(Address accountAddress)
{
string tableName = GetAssetTableName();
ExecutionContext.StateWriter
.TryGetTableReader<IPermissionedAssetTableReader>(
tableName,
out var permissionedAssetTableReader);
return permissionedAssetTableReader
.IsAccountRegistered(accountAddress);
}
This method is a queryable method and reads the data from the Asset table.
It is responsible for:
- Checking whether a specified address is registered in the Asset table or not
Since the Asset table is not read restricted, there is no need for any signatures for calling this method.
# GetAssetTableName
Method
static readonly string s_tableNamePrefix = "SampleAssetTokenTable";
public string GetAssetTableName()
{
return s_tableNamePrefix + "_" + InstanceName;
}
This is a queryable helper method to get the name of the Asset table which stores the digital tokens.
# Test the Smart Contract
# Test using CLI
This requires a running Miyabi node, either locally, or publicly accessible.
Please refer to this quick start guide to learn about how to test the deploy, instantiate, invoke, query and delete instance functionalities using the Miyabi CLI.
# Test using SDK
This requires a running Miyabi node, either locally, or publicly accessible. The Miyabi SDK NuGet packages can be used to create a test application to test various operations like contract deploy, instantiate, invoke, query and delete instance functionalities. A specific test program is implemented for testing the SampleContract.cs and can be found on GitHub (opens new window).
# Test using Contract Testing Kit
This is a NuGet package provided exclusively to specific Miyabi customers. It enables writing unit tests and debugging the smart contract code. It is a request-only NuGet package and not available to Miyabi playground users. Explanation about the Testing Kit is out of scope of this document.
# What more can I do?
A very simple digital token application was explained in this tutorial. Developers can make their own such applications with more complex data types, which are already implemented in Miyabi and ready to use. For example, it is very straight forward to make applications related to:
- NFT tokens
- Fungible tokens
- Simple and permissioned Binary data
- Document style data types having parent-child relationship
In Miyabi, smart contracts can also be used to communicate with other working smart contract instances:
- Invoke other smart contract instance's method
- Query other smart contract instance's method
Miyabi supports seamlessly upgrading smart contracts using the UpgradableContractBase
base class.
This base class provides additional methods which can be used to make upgrade transitions easy.