Deploying a Smart Contract on Mainnet

This tutorial details the process of deploying a Smart Contract in C# on the CirrusMain Sidechain. Dependant on the contract you are wishing to deploy, there may be additional steps required, this tutorial will focus on the deployment of a new Stratis Smart Contract and the steps required to have your contract deployed and executable on the Cirrus Sidechain.

Unlike other blockchain platforms, there is a level of auditing and review that is required by the public prior to the acceptance of a Smart Contract on the Cirrus Sidechain. This is achieved by whitelisting a hash of the developed contract, allowing for the validation of contracts that have been voted in approval.

Development of a Smart Contract in C#

The process begins by developing a Smart Contract that you want to deploy and utilize. For this example, the Standard Token Issuance contract will be utilized.

This contract has been designed to replicate the functionality offered by the ERC20 Token Contract.

using Stratis.SmartContracts;
using Stratis.SmartContracts.Standards;

/// <summary>
/// Implementation of a standard token contract for the Stratis Platform.
/// </summary>
public class StandardToken : SmartContract, IStandardToken
        /// <summary>
        /// Constructor used to create a new instance of the token. Assigns the total token supply to the creator of the contract.
        /// </summary>
        /// <param name="smartContractState">The execution state for the contract.</param>
        /// <param name="totalSupply">The total token supply.</param>
        /// <param name="name">The name of the token.</param>
        /// <param name="symbol">The symbol used to identify the token.</param>
        public StandardToken(ISmartContractState smartContractState, ulong totalSupply, string name, string symbol)
                : base(smartContractState)
                this.TotalSupply = totalSupply;
                this.Name = name;
                this.Symbol = symbol;
                this.SetBalance(Message.Sender, totalSupply);

        public string Symbol
                get => PersistentState.GetString(nameof(this.Symbol));
                private set => PersistentState.SetString(nameof(this.Symbol), value);

        public string Name
                get => PersistentState.GetString(nameof(this.Name));
                private set => PersistentState.SetString(nameof(this.Name), value);

        /// <inheritdoc />
        public ulong TotalSupply
                get => PersistentState.GetUInt64(nameof(this.TotalSupply));
                private set => PersistentState.SetUInt64(nameof(this.TotalSupply), value);

        /// <inheritdoc />
        public ulong GetBalance(Address address)
                return PersistentState.GetUInt64($"Balance:{address}");

        private void SetBalance(Address address, ulong value)
                PersistentState.SetUInt64($"Balance:{address}", value);

        /// <inheritdoc />
        public bool TransferTo(Address to, ulong amount)
                if (amount == 0)
                        Log(new TransferLog { From = Message.Sender, To = to, Amount = 0 });

                        return true;

                ulong senderBalance = GetBalance(Message.Sender);

                if (senderBalance < amount)
                        return false;

                SetBalance(Message.Sender, senderBalance - amount);

                SetBalance(to, checked(GetBalance(to) + amount));

                Log(new TransferLog { From = Message.Sender, To = to, Amount = amount });

                return true;

        /// <inheritdoc />
        public bool TransferFrom(Address from, Address to, ulong amount)
                if (amount == 0)
                        Log(new TransferLog { From = from, To = to, Amount = 0 });

                        return true;

                ulong senderAllowance = Allowance(from, Message.Sender);
                ulong fromBalance = GetBalance(from);

                if (senderAllowance < amount || fromBalance < amount)
                        return false;

                SetApproval(from, Message.Sender, senderAllowance - amount);

                SetBalance(from, fromBalance - amount);

                SetBalance(to, checked(GetBalance(to) + amount));

                Log(new TransferLog { From = from, To = to, Amount = amount });

                return true;

        /// <inheritdoc />
        public bool Approve(Address spender, ulong currentAmount, ulong amount)
                if (Allowance(Message.Sender, spender) != currentAmount)
                        return false;

                SetApproval(Message.Sender, spender, amount);

                Log(new ApprovalLog { Owner = Message.Sender, Spender = spender, Amount = amount, OldAmount = currentAmount });

                return true;

        private void SetApproval(Address owner, Address spender, ulong value)
                PersistentState.SetUInt64($"Allowance:{owner}:{spender}", value);

        /// <inheritdoc />
        public ulong Allowance(Address owner, Address spender)
                return PersistentState.GetUInt64($"Allowance:{owner}:{spender}");

        public struct TransferLog
                public Address From;

                public Address To;

                public ulong Amount;

        public struct ApprovalLog
                public Address Owner;

                public Address Spender;

                public ulong OldAmount;

                public ulong Amount;

Best Practice Guidelines

Stratis has no control as to what is whitelisted and what is not. Smart Contract developers should follow accepted best practices when writing new smart contracts. Some guidelines include:

  1. Use properties to store and retrieve data, with nameof(Property) as the PersistentState key.

  2. Ensure methods are only public when intended – especially property setters.

  3. Validate methods fail early with Asserts.

  4. Check the inside of the Asserts are logically correct.

  5. Check for possible integer overflows. By default, contracts are compiled with overflow checking enabled which will throw an exception if this occurs. If overflows are desired, explicitly specify this using an unchecked block.

  6. Decide whether you want your contract to accept the CRS Token sent from other contracts. If so, and your contract needs to do any extra accounting, be sure to override SmartContract’s Receive method.

  7. Generally, avoid loops and always avoid unbounded loops where the input of dynamic data could cause OutOfGas exceptions or other problems.

  8. Be aware of truncation when using integer division.

  9. Validate the result of transfers, sends or calls into other contracts - If they aren’t successful, you may not want to be updating the state of the contract being worked on.

  10. Perform any updates to the contract’s state after transfers to other addresses has occurred. This avoids re-entrance attacks.

  11. In general, make funds transfers in individual calls and require users to “pull” their CRS Token, rather than trying to push the funds as part of a wider method. This prevents malicious actors from denying payment to groups of users.

  12. Write unit tests using a mocking framework like Moq or NSubstitute. Use these to mock the properties on ISmartContractState and verify that the correct sequence of calls occur.

Testing with Moq and XUnit

The example below illustrates the key portions of testing your smart contract using Moq and XUnit.

First, we must create our test project and a test class. For this contract we’ll name it StandardTokenTests. We’ll define some private properties that hold common values that tests will use frequently and in the constructor of the class, set those values.

The first three properties have types of Mock<T> where we are defining properties that will hold mocks of the different Interfaces we will need in our tests. Then we’ll use the Setup method on mockContractState to set instances of the mocks we’ve created. There are other Interfaces than can and should be mocked when used such as:

  • IInternalHashHelper

  • IInternalTransactionExecutor

  • IMessage

  • ISerializer

public class StandardTokenTests
        private readonly Mock<ISmartContractState> mockContractState;
        private readonly Mock<IPersistentState> mockPersistentState;
        private readonly Mock<IContractLogger> mockContractLogger;
        private Address owner;
        private Address sender;
        private Address contract;
        private Address spender;
        private Address destination;
        private string name;
        private string symbol;

        public StandardTokenTests()
            this.mockContractLogger = new Mock<IContractLogger>();
            this.mockPersistentState = new Mock<IPersistentState>();
            this.mockContractState = new Mock<ISmartContractState>();
            this.mockContractState.Setup(s => s.PersistentState).Returns(this.mockPersistentState.Object);
            this.mockContractState.Setup(s => s.ContractLogger).Returns(this.mockContractLogger.Object);
            this.owner = "0x0000000000000000000000000000000000000001".HexToAddress();
            this.sender = "0x0000000000000000000000000000000000000002".HexToAddress();
            this.contract = "0x0000000000000000000000000000000000000003".HexToAddress();
            this.spender = "0x0000000000000000000000000000000000000004".HexToAddress();
            this.destination = "0x0000000000000000000000000000000000000005".HexToAddress();
   = "Test Token";
            this.symbol = "TST";

Now that the test class is setup and the constructor has set the properties you can begin writing your tests. See examples below or visit the full StandardTokenTests.cs file on Github.


For contract acceptance and whitelisting, it is important to test all successful and failing scenarios that come to mind. Smart contracts are immutable, once the hash is whitelisted or a contract is deployed, it cannot be modified.

Ensure Constructor Sets the Total Supply

public void Constructor_Sets_TotalSupply()
    ulong totalSupply = 100_000;

    // Set the message that would act as the initial transaction to the contract.
    this.mockContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, 0));

    // Create a new instance of the smart contract class.
    var standardToken = new StandardToken(this.mockContractState.Object, totalSupply,, this.symbol);

    // Verify that PersistentState was called with the total supply
    this.mockPersistentState.Verify(s => s.SetUInt64(nameof(StandardToken.TotalSupply), totalSupply));

Ensure TransferTo Full Balance Returns True

public void TransferTo_Full_Balance_Returns_True()
    ulong balance = 10000;
    ulong amount = balance;
    ulong destinationBalance = 123;

    // Setup the Message.Sender address
    this.mockContractState.Setup(m => m.Message)
        .Returns(new Message(this.contract, this.sender, 0));

    var standardToken = new StandardToken(this.mockContractState.Object, 100_000,, this.symbol);

    // Setup the balance of the sender's address in persistent state
    this.mockPersistentState.Setup(s => s.GetUInt64($"Balance:{this.sender}")).Returns(balance);

    // Setup the balance of the recipient's address in persistent state
    this.mockPersistentState.Setup(s => s.GetUInt64($"Balance:{this.destination}")).Returns(destinationBalance);

    Assert.True(standardToken.TransferTo(this.destination, amount));

    // Verify we queried the balance
    this.mockPersistentState.Verify(s => s.GetUInt64($"Balance:{this.sender}"));

    // Verify we set the sender's balance
    this.mockPersistentState.Verify(s => s.SetUInt64($"Balance:{this.sender}", balance - amount));

    // Verify we set the receiver's balance
    this.mockPersistentState.Verify(s => s.SetUInt64($"Balance:{this.destination}", destinationBalance + amount));

Ensure TransferFrom Greater Than Owners Balance Returns False

public void TransferFrom_Greater_Than_Owners_Balance_Returns_False()
    ulong balance = 0; // Balance should be less than amount we are trying to send
    ulong amount = balance + 1;
    ulong allowance = amount + 1; // Allowance should be more than amount we are trying to send

    // Setup the Message.Sender address
    this.mockContractState.Setup(m => m.Message)
        .Returns(new Message(this.contract, this.sender, 0));

    var standardToken = new StandardToken(this.mockContractState.Object, 100_000,, this.symbol);

    // Setup the balance of the owner in persistent state
    this.mockPersistentState.Setup(s => s.GetUInt64($"Balance:{this.owner}")).Returns(balance);

    // Setup the allowance of the sender in persistent state
    this.mockPersistentState.Setup(s => s.GetUInt64($"Allowance:{this.owner}:{this.sender}")).Returns(allowance);

    Assert.False(standardToken.TransferFrom(this.owner, this.destination, amount));

    // Verify we queried the sender's allowance
    this.mockPersistentState.Verify(s => s.GetUInt64($"Allowance:{this.owner}:{this.sender}"));

    // Verify we queried the owner's balance
    this.mockPersistentState.Verify(s => s.GetUInt64($"Balance:{this.owner}"));

Generate Contract Hash

To obtain a hash of a contract, you will need to run the Stratis Smart Contracts Test Tool to validate and compile the contract.

  1. Clone the Stratis.SmartContracts.Tools.SCT Repository

git clone
  1. Navigate to the Stratis Smart Contract Tools project directory

cd Stratis.SmartContracts.Tools.Sct\Stratis.SmartContracts.Tools.Sct
  1. Pass the path to the developed contract as a parameter. (In this example, the SRC20 Token Issuance contract is referenced)

dotnet run -- validate C:\CirrusSmartContracts\Mainnet\StandardToken\StandardToken\StandardToken.cs -sb
  1. The output from the above will provide you with both a hash and the byte code of the given contract.



Submitting a Smart Contract for review

In order for the newly developed Smart Contract to be deployable and executable on the Cirrus Sidechain, a review process needs to take place. The hash of the byte code needs to be accepted by the Sidechain Masternodes that produce blocks on the sidechain.

A Pull Request must be raised against the CirrusSmartContracts repository under the StratisProject organization. A link to the repository can be found below.

The Pull Request will need to conform to a template to provide a level of standardization for Sidechain Masternode operators and developers that may review the pull request.

An example can be found below.

PR Example

The Pull Request will need to contain a section for each of the below.

  • Description

  • Compiler

  • Hash

  • ByteCode

Smart Contract Review

A member of the Stratis Development Team and/or .NET Developers will provide comment on your pull request relating to the Smart Contract that has been developed.

These comments can range from advice to suggested changes to improve or validate the functionality of the Smart Contract. Comments provided on the pull request should remain constructive and relating to the contract itself, an example of a community developers comments can be found below.

PR Review

Where applicable recommendations should be taken into consideration or discussed on the pull request.

Acquiring the CRS Token

Whilst your Smart Contract has been fully developed, it cannot be deployed until it had been voted in by the Sidechain Masternode Operators. Once the Smart Contract hash has been whitelisted by the Sidechain Masternode Operators, the Pull Request will be merged to the repository. This highlights that the contract is available to be deployed by anyone on the Cirrus Sidechain.

Deploying the contract can be done from within the Cirrus Core wallet. The wallet can be downloaded and installed from the below release page.

Once you have installed Cirrus Core, you will need to run it either on testnet or mainnet. As this document refers to the StratisTest (TestNet) network, you will need to run the wallet in testnet.

On Windows
From the command line run the following command:
start "" "C:\\Program Files\Cirrus Core\Cirrus Core.exe" -testnet
On Mac
Open ScriptEditor to create a new applescript document and insert the following script:
do shell script "open /Applications/Cirrus\\ --args -testnet"

Save the script in the Applications folder with the name of and make sure to save it as an application type.

Once you have Cirrus Core running on testnet, you will need to follow the prompted steps to create a wallet, once this wallet has been created, you will be able to generate addresses that derive from the newly created wallet.

Cirrus Address

The Cirrus Token is required to deploy a Smart Contract, the amount is wholly dependant on the computational cost defined by the complexity of the Smart Contract.

Initially you will need to obtain a minimum of 1 STRAX and have it available within the Stratis Core wallet. The Stratis Core wallet can be downloaded and installed from the below release page.

Once you have installed Stratis Core, you will need to run it either on testnet or mainnet. As this document refers to the StratisTest (TestNet) network, you will need to run the wallet in testnet.

On Windows
From the command line run the following command:
start "" "C:\\Program Files\Stratis Core\Stratis Core.exe" -testnet
On Mac
Open ScriptEditor to create a new applescript document and insert the following script:
do shell script "open /Applications/Stratis\\ --args -testnet"

Save the script in the Applications folder with the name of and make sure to save it as an application type.

Once you have Stratis Core running on testnet, you will need to follow the prompted steps to create a wallet, once this wallet has been created, send a minimum of 1 STRAX to an address generated from the newly created wallet.

Stratis Core Dashboard

Once the balance is confirmed, you will need to perform a cross-chain transfer to the Cirrus Sidechain.

Stratis Core Send

As this document refers to the StratisTest (TestNet) network, the StratisTest and CirrusTest federation addresses are being utilized.

Federation detail for both test environments and production environments can be found below:

Production Environment
Stratis Federation Address: sg3WNvfWFxLJXXPYsvhGDdzpc9bT4uRQsN
Cirrus Federation Address: cnYBwudqzHBtGVELyQNUGzviKV4Ym3yiEo
Test Environment
Stratis Federation Address: 2N1wrNv5NDayLrKuph9YDVk8Fip8Wr8F8nX
Cirrus Federation Address: xH1GHWVNKwdebkgiFPtQtM4qb3vrvNX2Rg

The exchange of STRAX for CRS is known as a Cross-Chain Transfer. Each Cross-Chain Transfer will subject to an exchange fee of 0.001, meaning if you perform a Cross-Chain Transfer of 1 STRAX you will receive 0.999 CRS Tokens.

A Cross-Chain Transfer is also subject to a larger amount of confirmations, this is to cater for any reorganisations on the network and invalid credits being made on either chain. The confirmation times can be seen below.

STRAX to CRS: 500 Blocks (64 Second Block Time x 500 Blocks = 32000 Seconds ÷ 60 = 533 Minutes ÷ 60 = 8 Hours 48 Minutes)

CRS to STRAX: 240 Blocks (16 Second Block Time x 240 Blocks = 3840 Seconds ÷ 60 = 64 Minutes)

Once 500 Blocks have passed after making a Cross-Chain Transfer from STRAX to CRS you will see the CRS Balance appear in your wallet.

Cirrus Core Dashboard

Deploying a Smart Contract on Cirrus

You are now in a position whereby you have the following.

  • A Smart Contract in C#

  • The Smart Contract ByteCode

  • The Smart Contract Hash

  • A Cirrus Token Balance

This tutorial will continue with the assumption that you have also received sufficient votes from the Sidechain Masternodes to approve the deployment and execution of your Smart Contract. This will be evidenced by the Pull Request being merged to the repository.

Within Cirrus Core, navigate to the Smart Contracts tab.

Cirrus Core Create

You can now populate this page with information relative to the developed Smart Contract.

Amount: 0 (No funds being sent, so the amount can be set to 0)

Fee: 0.01 (A fee of 0.01 will allow the transaction to be mined successfully)

Gas Price: 100 (The amount of ‘satoshis’ to pay for each unit of gas spent. 100 is the minimum)

Gas Limit: 100000 (The maximum possible gas the contract can spend. This is the maximum)

In addition, you also need to specify the parameters of the contract, this will make the deployment of the contract unique.

The contract handles three parameters for deployment.

  • Token Supply (ULong)

  • Token Name (String)

  • Token Symbol (String)

Finally, you add the ByteCode that was retrieved earlier in the document and enter the password for the wallet.

Cirrus Core Create2

After ensuring your parameters are set correctly, select the Create Contract button to deploy the contract.

Querying a Smart Contract on Cirrus

The deployed contract will be included in the next block, once the transaction containing the Smart Contract is propagated to other nodes on the network. A successful deployment will be evidenced by history within the Smart Contracts tab.

Cirrus Core SC

By selecting the hash of the contract, you can view further detail regarding the Smart Contract deployment.

The address of the deployed contract will also be displayed as a value of the newContractAddress property.

Cirrus Core Receipt

You can now utilize the Call Contract functionality within Cirrus Core to interact with the contract you just deployed.

Cirrus Core Call

To retrieve the balance, you can use the GetBalance method, specifying an address as a parameter for the balance you wish to retrieve.

Cirrus Core Call2

Select the Call Contract button to query the contract

Cirrus Core Receipt2

The above action will generate a transaction querying the balance of the address provided. You will then be returned a receipt that contains the relevant information relating to the operation that was made.

In this instance you can see that the returnValue equates to the balance of the address specified; 100,000,000.