# 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 the Deploy 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 the ContractInstantiate 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. The StateWriter 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, the StateWriter 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 the StateWriter 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 the ContractInstantiate transaction entry is executed. The default implementation just returns true (expected output). If the method returns false 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 the ContractInstanceDelete transaction entry is executed. The default implementation for this method returns true (expected output). If the method returns false 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 the InternalTable.
  • TryGetInternalValue: This method can be used to get data from the InternalTable.
  • 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 return true

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.