Skip to content
abalu edited this page Oct 17, 2018 · 263 revisions

NEO Smart Contract Tutorial

How to write Neo Smart Contracts - A complete step-by-step guide

Table of contents

Goals, purpose, assumptions..

This tutorial is a step-by-step guide to start writing Smart Contracts for the NEO Blockchain. Getting the own code running on a Blockchain can be a brain-stretching and complex task. This tutorial should give you an easy-to-follow documentation to start developing NEO Smart Contracts as fast as possible. I will explain every facet of the process, to ease the entry barrier. My goal is to put together a complete guide that describes the easiest way a beginner can take to dive into this new technology. Let me know if I left something out, so that I can fill the gap. Shaping the future means thinking the future. The smart economy has a lot of challenges ahead, so let's put our heads together and bring it there where it belongs.

For this tutorial, I wrote two Smart Contracts. One is simple and one is advanced. I will use them as examples to explain the technical & the design aspects, that you should consider before writing a Smart Contract.

NeoSmartAddress

smartAddressLogo The NeoSmartAddress contract is simple but powerful. If you invoke the NeoSmartAddress contract from your wallet, you can create an alias for your public-key / wallet address. Every dApp or exchange that supports the NeoSmartAddress can then read your public address if the alias is provided. Precisely that means: everyone that invokes the NeoSmartAddress contract by passing the read operation and the given alias can get the belonging public address back from the Smart-Contract. Every NEO-Address can only register one identical NEOSmartAddress. You need to be signed-in to the wallet for which you want to create the NeoSmartAddress.

This has a lot of advantages over the actual public key:

  • NEO users don't need to copy-paste public keys from ..where-ever.. anymore
  • This Smart-Contract functions as a key store for the user
  • Sharing of a Neo-Address becomes easier
  • Memorizing the public address becomes possible, because the user can choose a name
  • This Smart-Contract could become a commonly accepted standard, if trusted wallets implement it

Of course, it also carries risks and has some downsides:

  • A fraud service could just pretend to invoke the Smart-Contract and return a random NEO public address. Fraud services are always an existing risk. Decentralizing trust means also decentralizing responsibility. To protect from this danger, one could only trust the service if the Transaction-ID which the service gets back when sending the InvocationTransaction to the network is provided. The Transaction-ID can be looked up at the Blockchain by the user. Trustable services or invoking the Smart-Contract yourself would prevent also from this risk.
  • Invoking the Smart Contract will cost a small fee.

Evolution steps: We could probably think of extending this Smart Contract to provide more ability for restrictions so that one could for example decide over the wallets that are allowed to get back the public Address for the given address.

NeoGasLiquid (advanced Smart Contract)

NGL_Logo I will discuss some design aspects of this Smart-Contract at the bottom of this article. You can find an extensive description here: https://github.com/abalu/neogasliquid

NEO Blockchain - NEO and GAS

NEO is the governing Coin of the NEO Smart Economy. For staking NEO in a personal Wallet you will receive GAS as a reward. GAS is the Token that powers the NEO Blockchain, the Utility-Token. Users of the Blockchain pay fees in GAS to deploy and run NEO Smart Contracts, since the computing resources consumed by the contract need to be paid. GAS is generated at a rate of 8 GAS per Block of the NEO Blockchain and is equally distributed to the NEO holders. The rate of production is reduced by 1 token for every 2 million blocks generated. Currently, about two Blocks in a minute are generated. If you launch your own ICO on the NEO Blockchain, then your Token is called NEP-5 (NEO Enhancement Proposal) Token.

How to write a Smart-Contract?

Why?

Start with why a smart guy taught us, so let's follow his advice.
Why would someone write a Smart-Contract?
To explain that you need to know what a Smart-Contract is.

What?

So, what is a Smart Contract?
A Smart-Contract is a piece of code that runs on a Blockchain.
The invocations of the published code are trackable, irreversible and decentralized. No third party is needed between the stakeholders, what ensures that no information is captured and manipulated by a middleman. The immutability and reliability of the code are inherent in the system. The store of the chain holds an indubitable and provable truth. Smart encryption enables the pseudonymization of the stakeholder. Smart-Contracts can be self-executing and follow an implemented process. So, trust becomes designable and finally programmable with a Smart-Contract. Decentralizing trust at the same time means decentralizing responsibility.

Why again?

So why would someone write a Smart-Contract?
The answer could not be more varied. To change the world? To depict the truth? To decentralize trust? To free the flow of information? To free herself from influencers? To free the world from influencers? To save the world? To create a programmable business? To build the smart economy.

What for?

What are good use-cases for Smart-Contracts? In my opinion, Smart-Contracts can best leverage their value when they process digital assets and digitizable artifacts in a useful manner. Digital assets include digital files & digital arts (audio, video, music, books, documents, designs, layouts..), digital spaces (advertising space, communities), digital storage, digital profiles, digital identities, software and solutions, digital communication (mail, messaging, ..), digital money (crypto,..). Digitizable artifacts mean everything that is being digitized: IOT & tangibles, hardware, devices, cars, votings, mowing machines, your heater, your fridge, toys, apartments, your identity, ...

So what?

Why can't someone steal your house or apartment? Because it is publicly known that it belongs to you and this is provable through a central instance. It will not prevent a criminal to break into your house and steal your ice-cream, but he can't steal your house. To steal it he has to reveal his identity and you need to confirm at the central instance that you want to transfer the ownership to him. What if your money and other things you own would have the same characteristics? If no one can steal your money because it's publicly known that it is yours? Simply spoken it is attached to your identity and I don't mean that it is just deposited somewhere only you have access to, I mean your face is stamped directly on the coins and bills. If you have your own, everywhere accepted, currency, then no one could steal it, because only you in person can access it and use it for payments.
No central instance can accomplish that. A blockchain powered decentralized and digital economy of the future probably could and possibly will one day.
Much easier than that is to start handling your already digitized things, today, of course.

Developing Stages (local, neo-privatenet, neo-testnet, neo-mainnet)

If you decide to write a Neo Smart Contract, then you will be very happy to discover that smart people in the NEO community (CityOfZion members) have prepared tools that will help you to develop on your local machine. IMHO you need to start there. It will allow keeping your developing cycles as short as possible (write, compile, test run). Here is an overview of the four stages that you will be happy to be able to go through.
Stages

How to set up the "local stage"

With the local stage, you will be able to develop your contract and debug it without even deploying it to a running Blockchain. I am focusing on C# on a Windows Machine for this tutorial.

local stage In this tutorial, the local stage consists of:

In the following, I will explain how to install all parts of the local stage and how to validate that the installation is properly done.

Visual Studio 2017

If you have already installed Visual Studio 2017 on your computer and checked for .NET Cross-Platform Development at the time of installation, you can skip this section.

Download and install: Visual Studio

"The installation process is very simple, just follow the operation prompts step-by-step. It should be noted that you need to check the installation of .NET Core cross-platform development, otherwise you will not be able to open neo-vm project." (docs.neo.org)

Install Visual Studio

Installation of .NET Core cross-platform development Installation of .NET Core cross-platform development

Installation of .NET Core cross-platform development Install Visual Studio

NeoContractPlugin

Installation method:

"Open Visual Studio 2017, open Tools, click on Extensions and Updates, click on the Online tab on the left side of the window, search NEO in the search box on the top right corner of the window, download the NeoContractPlugin." (docs.neo.org) Smart Contract Plugin

Install the neo-compiler

"Download the neo-compiler project on Github, open the solution with Visual Studio 2017, and publish the neon project."(docs.neo.org)

  1. Download: Neo-Compiler on GitHub

  2. Open the Visual Studio Project file Smart Contract Plugin

  3. Right-click on the neon project in Visual Studio and click publish Publish Neon

The publishing process should look like this Publish Neon

If this succeeded you should find the neon App in the subfolder of the project like this (probably you will have a Release folder instead of the here shown Debug folder - later more about that). Neon

  1. Add a System PATH variable that points to this neon App If you open the Command Line Interface and type "neon" you will discover that neon is still unknown to your machine. To register it you will have to add an entry at the bottom of the PATH System variable

neon unknown

The Path to your neon App should look pretty much like this: C:\projects\neo-compiler-master\neo-compiler-master\neon\bin\Release\netcoreapp2.0\publish\

If you have done this properly you will get this output when you type neon again at the CLI: neon known

If you have problems with this step I would advise you to watch this Video, in which Nikolaj-K explains this part of the installation part very accurate: video

Create a project

"After the above installation configuration is successful, you can create a NeoContract project in Visual Studio 2017. Once you create a project, it will automatically generate a C# file." (http://docs.neo.org/) Create project(http://docs.neo.org/)

Now you should be able to compile the sample project that looks like this: temp project

The compile output looks like this: Compile

Now you can find a .avm file in your repository. This is the compiled Smart Contract file. It is exactly the one that you use to deploy your project to the blockchain.
Compiled

At this point, you have a running configuration - that feels great for sure. You have almost finished the setup of the local stage. There is just one more thing I would recommend to install for really confident development on your local machine. The neo-debugger-tools brought to us by CityOfZion.

Installing the neo-debugger-tools

coz

The neo-debugger-tools contain a GUI debugger, that you can use to step into your Smart Contract and see what exactly is going on at a chosen time and scenario.

  1. Download neo-debugger-tools from Github
    Download: Neo-Debugger-Tools from GitHub

  2. Publish the project with Visual Studio
    Right-click on the project and hit publish. At this step, I had some troubles because my Visual Studio refused to compile unsafe code. I got a bunch of messages that said: "Unsafe code may only appear if compiling with /unsafe". What does /unsafe mean? The C# programming language doesn't allow you to work with pointers by default, without using the unsafe keyword to define an unsafe context. Unsafe code is handled outside of the Common Language Runtime. For invokes of native functions using pointers out of the context of the Common Language Runtime, you will need to use the "unsafe" keyword.
    So how can we run unsafe code in Visual Studio? Open the project's properties and select the Build property page. Check the Allow unsafe code checkbox. Try to publish the project again. unsafe_code And then I got another error: One file was banned from access by my local security setup (it was \NEO-Debugger\Forms\MainForm.resx). To allow for Visual Studio to access the file, I had to navigate to the file within Windows Explorer, right-click the file and correct the local properties to allow the access of the file. After that, I was finally able to publish the project.

  3. Change PATH System variable of neon
    After you have successfully published the neo-debugger-tools you will have to change the System variable PATH of you existing neon to the one that lies within the neo-debugger-tools.
    It will look similar to this: C:\projects\neo-debugger-tools-master\NEO-Compiler\bin\Release\
    You can check if you did it right with writing neon to you CLI.
    If it worked it tells you that you neon compile has debugger support now. neon debugger

  4. Restart Visual Studio and publish your project with debug configuration
    vs_debug_config
    Now that the neon compiler of the neo-debugger-tools is used, it will generate a map for the neo-debugger gui (neod) when you publish your project under debug settings. You can find the map in the *.debug.json file of the \bin\Debug\ build.
    In the provided NeoSmartAddress Smart Contract you can find the map under the following path NeoSmartAddress\bin\Debug\NeoSmartAddress.debug

  5. Open neo-debugger and load the .avm
    To start the Neo Debugger navigate to \neo-debugger-tools-master\NEO-Debugger\bin\Release\ and double click the neod Application. Open you compiled Smart Contract .avm file (File -> Open..). You should now get this view. vs_debug_config

  6. Have fun exploring the UI
    Congratulations! At this point, you have set up everything that you will need to develop valid Smart Contracts. With the Neo-Debugger you are able to test and debug them right on your local machine, without the need of deploying to a Blockchain-Network beforehand. How cool is that? In my opinion, you can not skip this step! The Neo-Debugger allows you to run and step into the execution of your code. It provides you insights into the storage of the Smart Contract and you can even view the assembler code (Debug -> Source -> Assembly) that is, in the end, the version of the code that the Chain will execute. It gives you options to handle CheckWitness and Triggers (more about that in the following chapters). Take your time and dive deeper into this great and essential tool.

If you could not follow my description I recommend you to watch this video in which Nikolaj-K explains this part of the installation very accurate: video

Pro-hint: Quick Cycle Edit-Compile-Debugging of C#.NEO Smart Contracts

There is one more hack that you can set up to save time when writing Smart Contracts with Visual Studio. Basically, you can set up your VS project to open the compiled .avm file within the neo-debugger when you run it.

How to accomplish this is very good explained in this video of Michael Herman: video

How to set up the CityOfZion neo-privatenet?

Moving to the next stage! Why?
If your Smart Contract is ready and you plan to deploy it, then you should move on to the next stage: the COZ neo-privatenet. It has a lot of advantages:

  • You have full control over all nodes, wallets and assets of the private neo-chain. How cool is that?
  • 100M NEO and lots of GAS under your fingertips, running real consensus and producing block after block fully accessible on your machine.
  • Thanks to docker - if something runs out of hand, you can easily set back everything to the initial state ;D
  • Move the universe and influence this journey with your ideas. Master the challenge and become a pioneer of this great project.

privnet tools

In this tutorial, the cityofzion/neo-privatenet stage consists of:

Let's start at the bottom - install Docker

If you are new to Docker, I have bad news and good news for you!
The bad news is: you are late ;D
The good news though is: you are lucky ;D It hasn't ever been easier to run Docker, stable on Windows than nowadays.

Basically, you download and install it. No Linux in between needed anymore. Just follow this instruction: Link: Install Docker for Windows Still, there are two things I would like to point out:

  • "Virtualization must be enabled in BIOS and CPU SLAT-capable. Typically, virtualization is enabled by default."
    My experience though is that it is not so enabled by default. So what you have to do is to enter BIOS during startup of your machine and enable virtualization. Don't forget to save on exit!
  • The second thing that usually happens is that your firewall blocks the needed Docker ports. But as I already mentioned, I have to confess that things work really brilliant with Docker on Windows and so Docker will detect your firewall and let you know as describe here: https://docs.docker.com/docker-for-windows/#shared-drives

Here you can find everything that you need to get started with docker: https://docs.docker.com/docker-for-windows/

Install Docker-Tool Box (optional)

If you are new to Docker and you are used to graphical interfaces the Docker Tool Box could make your first steps easier. Within the Docker-Tool Box, you will find Kitematic. It is a Windows Desktop GUI for docker. It allows you to start, stop, pause containers by clicking. It shows you the container logs and lets you easily open a CLI inside a container.

Here you can find everything that you need to get started with the Docker-Toolbox: https://docs.docker.com/toolbox/toolbox_install_windows/

Install NEO GUI

Download the latest Version of the Neo-Gui from here: Neo-Gui

The client itself does not require an installation. After downloading and unzipping the files to a directory of your choice, double-click on neo-gui.exe that opens the client.

I got this prompt when I opened the NEO GUI the first time. neo gui prompt
Click "yes" to install the certificates.

The NEO GUI comes preconfigured for the NEO mainnet. For switching that configuration to testnet or to the local privatenet you have to edit the content of two files: config.json and a protocol.json You will find them in the NEO Root folder. As you will recognize that there are also config.mainnet.json, config.testnet.json, protocol.mainnet.json and protocol.testnet.json. These files serve as a backup for you if you mess up something with the configuration. The filename indicates which content you have to put into the two files (config.json and a protocol.json) depending on which network you want to connect to.

To make it work with your docker-privatenet you will have to edit the two files like this:

config.json

  "ApplicationConfiguration": {
    "Paths": {
      "Chain": "Chain",
      "Notifications": "Notifications"
    },
    "P2P": {
      "Port": 20333,
      "WsPort": 10333
    },
    "RPC": {
      "Port": 30333,
      "SslCert": "",
      "SslCertPassword": ""
    },
	"DataDirectoryPath": "ChainTestNet",
    "CertCachePath": "Certs",
    "NodePort": 20333,
    "WsPort": 20334,
    "UriPrefix": [ "https://localhost:20332" ],
    "SslCert": "",
    "SslCertPassword": "",
  }
}

protocol.json

  {
    "ProtocolConfiguration": {
        "Magic": 56753,
        "AddressVersion": 23,
        "SecondsPerBlock": 15, 
        "StandbyValidators": [
            "02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
            "02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
            "03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
            "02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62"
        ],
        "SeedList": [
            "127.0.0.1:20333",
            "127.0.0.1:20334",
            "127.0.0.1:20335",
            "127.0.0.1:20336"
        ],
        "SystemFee": {
            "EnrollmentTransaction": 1000,
            "IssueTransaction": 500,
            "PublishTransaction": 500,
            "RegisterTransaction": 10000
        }
    }
}

No matter on which stage you are operating on the client must be fully synchronized with the blockchain before it can be used. The lower left corner shows the number of synchronized blocks as well as the total number of blocks (see picture below). These numbers are constantly updated since the Blockchain Data is downloaded and saved to the \Chain folder. If you switch between stages, you have to remove or displace this Chain data, otherwise the NEO GUI will not work properly. Also, delete the old peers.dat from the root folder. neo-gui

Congratulations! Your NEO-GUI is ready to run on the COZ privatenet. It has many functionalities you should discover at this stage. My time will not allow writing about them in more details here. Now let's bring up the docker container so that we can see this in live in action.

Docker run the NEO privatenet

Get a copy of this neo-private-docker
Link: Github: neo-privatenet-docker
Link: Docker: neo-privatenet-docker

Clone or download it. Open your CLI and navigate into the root folder.
Type docker run --rm -d --name neo-privatenet -p 20333-20336:20333-20336/tcp -p 30333-30336:30333-30336/tcp cityofzion/neo-privatenet

Something like this should happen and take quite a while: docker_run

After the container is up and running, you can open Kitematic from the Docker Toolbox. It will give you a nice UI view of the docker-container and provide some nice features like open a bash inside the container by clicking, watch the containers log and so on. But for this tutorial, we will stay at the Win CLI.

To jump into the Container and open a bash, type:
docker exec -it neo-privatenet /bin/bash

You should get this: bash
Cheer! It says we have neo-python installed inside the container - yes!
Thank you metachris, you are a genius.

You can simply try: screen -ls and see what you get.

Type the following to reset the Chain-Data:
rm -rf root/.neopython/Chains/privnet*

Ok now, as we have been asked to call it - do it - type:
neopy

Boom! NEO CLI!
neo cli I am always happy to see this small friend neo> again.

So here we have also the neo-cli running inside this container and we don't have to install it.

Note this little friend down here - It shows you the progress of the privatenet. This is also known as the block height of the blockchain.
running_blocks
This means you are running a private neo blockchain on your device. I hope it feels great ;D

Type: help neo_cli_help

This list shows you all the commands which are now available to you.

Ready for the next great thing? Let us get access to some assets. Type:
open wallet neo-privnet.wallet
Password is: coz
Type: wallet

loggedintowallet

Note the balance! All NEO and a lot of NEOGas is your's. Ok, so let's spend some tokens! If you navigate to the neo-privatenet-docker-master folder you will find a wallets folder. Inside of that folder, you will find 4 Wallets that have been already bootstrapped to the blockchain and you can use just right now. The passwords of the wallets are: one, two, three, four - according to the named wallet.

Let's send some tokens to wallet 2
Type: send neo AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17 10000
Password is: coz
Type: send gas AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17 10000
Password is: coz

Now let's open the NEO-GUI and let it synch to our running Blockchain. As described before, just right after startup, your NEO-GUI should connect to the 4 Nodes and start synching. You are ready to go when all the numbers in the lower left corner are equal. Click Wallet -> Open Wallet Database, browse to the wallet2.json and type password two. Now you should be able to see the balance and transaction that you just sent. Since NEO needs just one confirmation - it is already there - what else ;D
wallet1

Congratulation - you have successfully set up the CityOfZion neo-privatenet! I am sure you will be able to find out for yourself how to stop it (p.e. docker kill). Let's move on and deploy a Smart Contract on this stage.

Deploying a Smart Contract to the CityOfZion neo-privatenet with NEO-GUI

  • Open the NEO-GUI

  • Log-in to a Wallet that holds enough GAS, as described before

  • Click Advanced and select -> Deploy Contract... to display the Deploy Contract dialog box.

  • Click Load from the dialog box and select your compiled SmartContract *.avm file from your Visual Studio project.
    You should find it in the subfolder \bin\Release\*avm.
    deploywithneogui deploystep2

  • Fill in the Information form. All fields are mandatory.

  • Now the Metadata fields: they are the most important part of this dialog box. Find an explanation of this data right in the next section.

  • Check the Checkbox Need Storage if your Smart-Contract will Store Data (Yes, if your Code contains Storage.Put or Storage.Get)

  • Check the Checkbox Need Dyncall if your Smart-Contract needs dynamic invocation.

  • Copy the Script Hash - You need it to invoke and find the Smart Contract on the Blockchain.

  • Hit Deploy

deploystep3

  • To complete the deployment, click Test and then Invoke on next dialog box.
  • Finally the NEO-GUI will give you the Transaction (TXID) of your deployment.
  • As soon as the Transaction it is confirmed, your Smart-Contract is available for invokes on the Blockchain. You can see it also in the Transaction History of your wallet.

deploystep4

Note! Once your Smart-Contract is deployed it can't be changed anymore.

Parameters and Return Types

The Parameter list defines what input types your Smart-Contract will accept. The Return Type tells what data type will the Smart-Contract-Invoker get back as a result of his call. The types are defined as 2 hexadecimal characters for each parameter. Here is a lookup list for the params and return types.

Value Parameter / Return Type
00 Signature
01 Boolean
02 Integer
03 Hash 160 (160-bit hash value)
04 Hash 256 (256-bit hash value)
05 Byte Array or Object
06 PublicKey
07 String
10 Array
f0 InteropInterface
ff Void

To identify the Parameter list and the Return Type that you need to provide, you have to take a look at the invocation start point of your Smart-Contract. This is your Main method.
The types that are written inside the () of your Main define your Parameter List.
For example: (string, bool, int)
Will result in: 070102
Because: string = 07, bool=01, int = 02

What type does your Main return?
This is your Return Type.
For example public static object Main ()
This means the Return Type is 05
Because: object = 05

Testinvoking a Smart Contract from the NEO-CLI

This tutorial is only sexy if you are finally able to invoke your deployed Smart-Contract.
And here is how you do it..

Turn on the SmartContractEvent logging. It will allow you to see exactly what is happening when you test invoke the Smart-Contract.
Type: config sc-events on

And now let's invoke the Smart-Contract.
This doesn't look very complicated, but you have to be very accurate with the params that you pass in, otherwise you will get empty Results. Believe me, I spent a whole day figuring out what I was doing wrong 🥇
The command patterns is: testinvoke {contract_hash} param1 param2..
So in the case of the SmartAddress Smart-Contract this means:
testinvoke ScriptHashOfContract create ['myNeoSmartAddress', 'myPublicAdress']

When i put everything together it looks like this:
testinvoke 0x12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0 create ['mine', 'AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y']

In the first place, the invoke is only done locally. It will not be run on the network until you input your password. If you do not want to invoke on the network, you can simply input an incorrect password and it will cancel.

Here is what you will get as output

[I 180921 20:00:49 EventHub:71] [test_mode][SmartContract.Storage.Get] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] ['AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y -> 0']
[I 180921 20:00:49 EventHub:71] [test_mode][SmartContract.Storage.Get] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] ["b'mine' -> bytearray(b'')"]
[I 180921 20:00:49 EventHub:71] [test_mode][SmartContract.Storage.Put] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] ['b\'mine\' -> bytearray(b\'#\\xba\\\'\\x03\\xc52c\\xe8\\xd6\\xe5"\\xdc2 39\\xdc\\xd8\\xee\\xe9\')']
[I 180921 20:00:49 EventHub:71] [test_mode][SmartContract.Storage.Put] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] ['AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y -> 1701734765']
[I 180921 20:00:49 EventHub:71] [test_mode][SmartContract.Execution.Success] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [1]
Used 2.498 Gas

-------------------------------------------------------------------------------------------------------------------------------------
Test invoke successful
Total operations: 137
Results ['1']
Invoke TX GAS cost: 0.0
Invoke TX fee: 0.0001
-------------------------------------------------------------------------------------------------------------------------------------

Enter your password to continue and invoke on the network

[password]> ***

We can see that the Test-Invocation was successful, that the parameters have been parsed correctly and the Result-Set is 1, that means that the Contract will return `true` when we invoke it.
When test-invoking with `sc-events on` we see the SmartContract.Execution.Success event, and also that the event was done in `test_mode`.

So since we really want to execute this on the Chain and Store our alias in the Smart-Contract, we type the PW: coz.
And then we get this..

[I 180921 20:01:15 Transaction:613] Verifying transaction: b'7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d'
[I 180921 20:01:15 EventHub:89] [SmartContract.Verification.Success][9405] [e9c05a105da0874cfa2d36b58285adec68f764b5] [tx 7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d] [True]
Relayed Tx: 7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d

What we see here is the SmartContract.Verification.Success event. This tells us that the TX was signed correctly and will pass Verification. At the bottom we get the InvocationTransaction as "Relayed Tx", which we can use to look up the Invocation, telling us that our request has been sent to the Blockchain for confirmation during consensus.
With sc-events on we can just wait for the first confirmation because itt will produce one more output:

[I 180921 20:01:41 EventHub:89] [SmartContract.Storage.Get][9406] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [tx 7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d] ['AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y -> 0']
[I 180921 20:01:41 EventHub:89] [SmartContract.Storage.Get][9406] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [tx 7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d] ["b'mine' -> bytearray(b'')"]
[I 180921 20:01:41 EventHub:89] [SmartContract.Storage.Put][9406] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [tx 7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d] ['b\'mine\' -> bytearray(b\'#\\xba\\\'\\x03\\xc52c\\xe8\\xd6\\xe5"\\xdc2 39\\xdc\\xd8\\xee\\xe9\')']
[I 180921 20:01:41 EventHub:89] [SmartContract.Storage.Put][9406] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [tx 7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d] ['AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y -> 1701734765']
[I 180921 20:01:41 EventHub:89] [SmartContract.Execution.Success][9406] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [tx 7794a73bc9d18942a662ee84da35f5459dad03df24ebcfd88c30f5c5ad2c799d] [1]

After the TX has been processed by the network, we get details of what has been done and the SmartContract.Execution.Success in real-mode ;D
The last signs [1] indicate that the return of the Smart-Contract is true.
Great!Looks like we have just stored our first NEOSmartAddress. So let's do another invocation and try to get the public address with the read operation.
testinvoke 0x12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0 read ['mine']
Here is what we get:

[I 180921 20:08:59 EventHub:71] [test_mode][SmartContract.Storage.Get] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] ['b\'mine\' -> bytearray(b\'#\\xba\\\'\\x03\\xc52c\\xe8\\xd6\\xe5"\\xdc2 39\\xdc\\xd8\\xee\\xe9\')']
[I 180921 20:08:59 EventHub:71] [test_mode][SmartContract.Execution.Success] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9']
Used 0.16 Gas

-------------------------------------------------------------------------------------------------------------------------------------
Test invoke successful
Total operations: 76
Results ['23ba2703c53263e8d6e522dc32203339dcd8eee9']
Invoke TX GAS cost: 0.0
Invoke TX fee: 0.0001
-------------------------------------------------------------------------------------------------------------------------------------

Enter your password to continue and invoke on the network

[password]> ***

We see that the request is valid and enter the password.
[I 180921 20:09:15 Transaction:613] Verifying transaction: b'2a4e01f7676e507529700328d39106e3ebc0da8eb1db6692451b20a482e801d5'
[I 180921 20:09:15 EventHub:89] [SmartContract.Verification.Success][9430] [9722885e3b991e27f5c9ec70268e34aafa243506] [tx 2a4e01f7676e507529700328d39106e3ebc0da8eb1db6692451b20a482e801d5] [True]
Relayed Tx: 2a4e01f7676e507529700328d39106e3ebc0da8eb1db6692451b20a482e801d5

We get the Verification.Success and wait for the confirmation.
Finally we get this:
[I 180921 20:09:39 EventHub:89] [SmartContract.Storage.Get][9431] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [tx 2a4e01f7676e507529700328d39106e3ebc0da8eb1db6692451b20a482e801d5] ['b\'mine\' -> bytearray(b\'#\\xba\\\'\\x03\\xc52c\\xe8\\xd6\\xe5"\\xdc2 39\\xdc\\xd8\\xee\\xe9\')']
[I 180921 20:09:39 EventHub:89] [SmartContract.Execution.Success][9431] [12ff8b4749bdf2553d90bcd5992a4ff8629ebdb0] [tx 2a4e01f7676e507529700328d39106e3ebc0da8eb1db6692451b20a482e801d5] [b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9']

Looks nice, but we have expected the public key. What is this?

#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9
It is the Script Hash of the address - this means just an other way of writing it.
So let's check if this is the correct one.
Type: wallet
The first line of the result is where you can find the Script Hash of the wallet.
And it looks like this:
[I 180921 20:11:55 UserWallet:538] Script hash b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9' <class 'bytes'>
Nice!
We have successfully invoked our own deployed contract on our neo privatenet from CLI.
I hope you feel as happy as I do, when i get at this point.

Testinvoking a Smart Contract from the NEO-GUI

At this point, I have to tell you, that sometimes it's difficult to set the right parameters with the NEO-GUI. That's why invokes work better from prompt, but still, let's have a look how it works with the NEO-GUI.

  • Select Advanced -> Invoke Contract.. <>br/> neo-gui-invoke

  • Fill in the Script Hash of the Contract to load the Parameter-Settings. neo-gui-invoke1

  • Click on the three dots "..." to open the dialog window for Parameter configuration.

  • Set the Parameter you need neo-gui-invoke2

  • When you are ready, click Test to see the Result-Set of the Test-Invoke. If everything is satisfying, click invoke to perform the invocation on the network.

  • Finally you get a TXID. neo-gui-invoke3

Writing a basic Smart Contract?

In this section, we will line by line go through the code of the NEOSmartAddress Code and analyze what it does. You will recognize that the operations follow the four basic functions of persistent storage (CRUD - create, read, update, and delete). If you want to write advanced code, you need to know the elementary.
What will you learn from this section:

  • Smart-Contract invocation point
  • Design of operations
  • Caller validation
  • Storage & Storage context (Put, Delete)

Ok, here we go.

The Main

public static object Main(string operation, params object[] args)
        {
            switch (operation)
            {
                case "create":
                    return Create((string)args[0], (byte[])args[1]);
                case "read":
                    return Read((string)args[0]);
                case "update":
                    return Update((string)args[0], (string)args[1]);
                case "delete":
                    return Delete((string)args[0]);     
                case "isFree":
                    return IsFree((string)args[0]);
                default:
                    return false;
            }
        }

The Main is the Smart-Contract invocation point. In the Smart-Contract deployment, you need to specify the parameters and returns types of the Smart-Contract as described above. Smart-Contract parameters are byte types and in this case, the parameters are 0710 and return type is 05.
Here is why:
"public static object Main" indicates that the invoked method will return an object type an this is listed as 05.
As input Parameters this is specified: "(string operation, params object[] args)". A string is 07 and an array is 10. No matter how many Parameters you have just put them together in the right / desired sequence.

At the next line, you will find a switch (operation). The Smart-Contract accepts the operations as strings. We put the string into a switch-selection statement that chooses a single switch section to execute from a list of candidates based on the pattern match with the match expression ("operation"). It is basically a simplified expression of several elseif sections. So this is the control center of the Smart-Contract. It's the only way in from outside to the executing contents. All our functions are private, so access from any other thinkable way is excluded and this is very important. This design pattern you should definitely use, to secure your Smart-Contract from undesirable calls.

Create

private static bool Create(string neoSmartAddress, byte[] neoAddress)
        {
            if (!Runtime.CheckWitness(neoAddress)) return false;
            byte[] free = Storage.Get(Storage.CurrentContext, neoAddress);
            if (free != null) return false;
            free = Storage.Get(Storage.CurrentContext, neoSmartAddress);
            if (free != null) return false;
            Storage.Put(Storage.CurrentContext, neoSmartAddress, neoAddress);
            Storage.Put(Storage.CurrentContext, neoAddress, neoSmartAddress);	
            return true;
        }

The create functions has two input parameters: the desired NEOSmartAddress (string) and the Neo-Address as byte[] for the Wallet Address the alias should be stored for.
Runtime.CheckWitness
The first thing we want to check is if the owner of neoAddress is the caller of this method because otherwise, we will not allow creating a NEOSmartAddress. Thankfully there is a validation function:
boa.blockchain.vm.Neo.Runtime.CheckWitness This method accepts a single parameter: the address that you would like to validate. All three representation formats (Script Hash, Public Key, Address) of the wallet will work the same.

Here is an example of the three representations of the same Walet-Address:

Script hash b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9'
"public_keys": [
        {
            "Address": "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
            "Public Key": "031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a"
        }
    ]

Storage
Namespace: Neo.SmartContract.Framework.Services.Neo
Assembly: Neo.SmartContract.Framework
At the next Line Storage.Get(Storage.CurrentContext, neoAddress); gets the store of the given neoAddress inside of the Smart-Contract. With that call we want to check if a NEOSmartAddress is already stored for the given neoAddress.
The Storage Class provides us the possibility to persistent data in the store of a Smart-Contract. Precisely we are able to store key-value pairs in this form:
(byte[], byte[])
(string, byte[])
(byte[], string)
(string, string)

As you may notice there is also the Storage.CurrentContext property. It returns the current store context. If you write advanced Smart-Contracts that interoperate with each other, you can share the Storages among themselves with this property. A Smart-Contract could invoke another one and pass in the Storage-Context as an Input-Parameter. The other Smart-Contract then could perform CRUD functions on that passed Storage.

What is happening in the rest of the create() lines?

// Check if the neoAddress has already created a neoSmartAddress (use update instead)
// Get the store of the desired neoSmartAddress
// Check if the neoSmartAddress is already taken
// Save the desired neoSmartAddress for given neoAddress
// Save the neoSmartAddress also to the key of neoAddress 

Doubling the Storage at the last two lines? Why's that?
When a NEOSmartAddress is created in this Smart-Contract the final Store will look like this:
Storage
We need to access the data from both directions. It's obvious that we want to perform a read() with the NEOSmartAddress, because this is the whole purpose of the Smart-Contract. But we also want to ensure that one neoAddress is able to register only one NEOSmartAddress. For that, we need to check if it already has registered one before we allow the registration of a new one. I believe that access by key is much faster than browsing through the whole data to find one value and because of that, it's perfectly acceptable to double the storage. At least I believe it is the better decision since computing power on the chain is the most expensive part.

Read

Returns the neoAddress (byte[]) for the given neoSmartAddress (string) and is simple as this:

   private static byte[] Read(string neoSmartAddress)
        {
            return Storage.Get(Storage.CurrentContext, neoSmartAddress);
        }

Update

The Update function changes the actual NEOSmartAddress to a new one. It returns true if everything went good, and false instead. Here is the Code:

    private static bool Update(string neoSmartAddressFrom, string neoSmartAddressTo)
        {
            byte[] neoAddress = Storage.Get(Storage.CurrentContext, neoSmartAddressTo);
            if (neoAddress != null) return false;
            neoAddress = Storage.Get(Storage.CurrentContext, neoSmartAddressFrom);
            if (neoAddress == null) return false;
            if (!Runtime.CheckWitness(neoAddress)) return false;
            Storage.Put(Storage.CurrentContext, neoSmartAddressTo, neoAddress);
            Storage.Put(Storage.CurrentContext, neoAddress, neoSmartAddressTo);
            Storage.Delete(Storage.CurrentContext, neoSmartAddressFrom);
            return true;
        }

Here is what happens line by line:

// Check if new neoSmartAddress is free
// The desired neoSmartAddress is already taken - return false
// Get the owner of the neoSmartAddressFrom
// If actual neoSmartAddress is empty no owner was found / invalid input - return false
// Check if the owner of neoAddress is the caller of this function
// Update store	
// Update store	
// Delete old Address
// return true;

If you throw just a fast look on the Code you'll probably notice that we store two values, but we delete only one. This is because we update the existing value directly with Storage.Put(Storage.CurrentContext, neoAddress, neoSmartAddressTo);. So there is no need to delete two values.

Delete

The Delete function deletes a NEOSmartAddress. It returns true if everything went good, and false instead. Here is the Code:

private static bool Delete(string neoSmartAddress)
        {
            byte[] ownerOfNeoSmartAddress = Storage.Get(Storage.CurrentContext, neoSmartAddress);
            if (ownerOfNeoSmartAddress == null) return false;
            if (!Runtime.CheckWitness(ownerOfNeoSmartAddress)) return false;
            Storage.Delete(Storage.CurrentContext, neoSmartAddress);
            Storage.Delete(Storage.CurrentContext, ownerOfNeoSmartAddress);
            return true;
        }

Here is what happens line by line:

// Get the owner of the neoSmartAddress
// Can not be deleted if neoSmartAddress is not taken - return false
// Check if the owner of neoSmartAddress is the caller of this function - else return false
// Delete from store 
// Delete from store 
// return true;

At this point we need to delete both key-value pairs to clear up the store.

Writing an advanced Smart Contract?

In this section, I would like to address a few key points that are very useful when a Smart-Contract grows wings.

  • Admin Features and owner control
  • Designing the State Machine
  • Runtime.Trigger (Verification, Application)

Admin Features / Owner Control

We have learned that Smart-Contracts are immutable. But this does not mean that we can not foresee dynamic parts by design. As the owner of the Smart-Contract, you probably want to have some admin features to accomplish control or updates to some parts of the Contract. Maybe you want to adopt set fees, change the state of the Smart-Contract, enable and disable features and so on. You are the one who has to decide what those actions should be.
So how can this be accomplished?

First store the public key, address or script hash of the admin in your Smart-Contract like this:

//Admin Hash
private static readonly byte[] Admin = "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y".ToScriptHash();

Inside the invocation part, you can then put the admin functions and executions inside a condition that checks that the caller is the admin:

if (Runtime.CheckWitness(Admin))
            {
                      ...
            }

Of course, you can use this pattern to specify more roles or groups and addresses to control the Smart-Contract during its lifetime.

Designing the State Machine

For an owner of a Smart-Contract, it could be useful to have the ability to put Smart-Contracts into different states (e.g. active, deactivated, pending...). States could differ by the possibilities of callable operations. If the Smart-Contract becomes obsolete, you may want to deactivate it, so that no operations are callable anymore (e.g. except a ‘reactivation’ for the owner – just in case 😉) This means you design what operations should be possible at which state. Drawing a State Machine can help through the design process if this becomes more complicated.

So how would this be realized in code?
First you need to define your desired states like this:

 /* Contract States 
  * A state of the Smart-Contract is put to the Neo.Storage
  * The Owner is able to turn the Smart-Contract into inactive state
  */
private static readonly byte[] Initial = { };         // at this state "initialize" is the only callable operation
private static readonly byte[] Active = { 0x01 };     // all operations open
private static readonly byte[] Inactive = { 0x02 };   // suspended - only limited actions

Then in the Admin operations sections you can put the state to the Storage like this:

 // *** Admin Operations ***
if (!Runtime.CheckWitness(Admin))
   {
    Runtime.Log("Owner verification failed");
    return false;
    } else {
           if (operation == "suspend")
           {
           Storage.Put(Context(), "state", Inactive);
           return true;
           }
           if (operation == "continue")
           {
           Storage.Put(Context(), "state", Active);
           return true;
           }
   }

You could have an function like this to get the actual state:

private static byte[] GetState()
        {
            return Storage.Get(Context(), "state");
        }

And then you can validate the state wherever you need to like this:

 if (GetState() != Active) return false;

You can use this code pattern also for designing a digital process that is executed within the Smart-Contract. Usually, one that involves more parties and generates trust between them. Every state could hold its tasks that can be executed to move to the next state. The invokers are able to track the status of the process and have clear insights into the irreversible truth on the chain.

Runtime.Trigger

The Runtime.Trigger is the mechanism that triggers the execution of your Smart-Contract. The purpose of your Smart-Contract defines the trigger that has to be used. There are four triggers:

  • Application
  • Verification
  • AppliactionR
  • VerificationR

Application

"An application trigger is used to invoke the contract as a verification function, which can accept multiple parameters, change the blockchain status, and return values of any type." (http://docs.neo.org) If you use an Application trigger you want to perform CRUD operations on the storage and return any data type based on the processed given input. I have demonstrated the trigger's behavior in the invoking section of NEOSmartAddress. The trigger is triggered when the invocation is sent. The Application trigger first confirms the transaction, then executes the Smart-Contract and finally delivers the SmartContract.Execution.Success message.

As mentioned before the main function is the invocation point of the Smart-Contract. You can access the trigger from inside the main function and check which trigger type has invoked the Smart-Contract.

Verification

The verification trigger is triggered when sending a transfer. It is not invoked with an InvokationTransaction and returns a boolean value, indicating the success of the transaction. When you use it in an exclusive way your Main function will have the return type boolean.
It will probably look similar to this:

public static bool Main(byte[] signature)
{
    if (Runtime.Trigger == TriggerType.Verification) //check if trigger is verification
    {
            var transaction = (Transaction)ExecutionEngine.ScriptContainer;
            if (IsWithdrawingAsset(transaction)) 
            {
              //proceed withdrawal 
              return true;
            }
            else
                return false;
    }  
}

VerificationR

Now it becomes a little bit more complicated! With the two Trigger mentioned above "there is no way for smart contracts to refuse a transfer, or to modify the states of the blockchain while accepting a transfer" (Proposal for NEP-7).
VerificationR has the same return type (boolean) and behaves same as the Verification trigger. The only difference is that it is being invoked because someone or something specified your Smart-Contract as a target of an output of the transaction.
So, for example, this Trigger is activated automatically when something or someone tries to send assets to your Smart-Contract. Then you can "catch" the Trigger event and handle it the way you need to and return a boolean value that indicates the validity of the transaction.
VerificationR will call the main function of your Smart-Contract like this:
main("receiving", new object[0]);
So you should foresee a method that looks like this to handle the call:
public bool receiving()

I have to confess that I have not used this trigger myself and I plan to document it here after my first try.

ApplicationR

ApplicationR differs from the Application trigger because it accepts no parameter. It is triggered when a transaction is processed which has the received function as the target output. ApplicationR will call the main function of your Smart-Contract like this:
main("received", new object[0]);
So you should foresee a method that looks like this to handle the call:
public byte[] received()

I have to confess that I have not used this trigger myself and I plan to document it here after my first try.

Mixing triggers in one Smart-Contract

Yes, this is possible!
You can put Application and Verification trigger in one Smart-Contract like this:

public static object Main(string operation, params object[] args)
        {
            if (Runtime.Trigger == TriggerType.Verification) //Verification Set   
            {
                // ContractTransaction received
                //Withdraw asset
                //get the transaction
                var transaction = (Transaction)ExecutionEngine.ScriptContainer;
                if (!IsWithdrawingAsset(transaction)) return false;

                // Get the withdrawal destination address
                var destinationAddress = GetTransactionDestinationAddress(transaction);

                // Verify the outputs 
                var transactionOutputs = transaction.GetOutputs();
               
                ... //proceed transaction

            } else if (Runtime.Trigger == TriggerType.Application) //Application Set
            {
                // InvocationTransaction received
                // *** Initialization ***
                if (operation == "initialize")
                {
                    if (!Runtime.CheckWitness(Admin))
                    {
                        Runtime.Log("Owner verification failed");
                        return false;
                    }

               ... //proceed application
                }
              }
          }

The end.
Thank you for reading! I hope you enjoyed it!