Skip to content

Introduction to Smart Contracts

A Simple Smart Contract

Let us begin with the most basic example. It is fine if you do not understand everything right now, we will go into more depth later.

Storage Example

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.34;

contract SimpleStorage {
    uint storedData;

    function set(uint x) public {
        storedData = x;
    }

    function get() public view returns (uint) {
        return storedData;
    }
}

The first line tells you that the source code is licensed under the MIT license. Machine-readable license specifiers are important in a setting where publishing source code is the default.

The next line specifies that the source code is written for Solidity version 0.8.34 or newer.

A contract in the sense of Solidity is a collection of code (its functions) and data (its state) that resides at a specific address on the blockchain. The line uint storedData; declares a state variable of type uint (unsigned integer). You can think of it as a single slot in a database that you can query and alter by calling functions of the code that manages the database.

💡 NeoVM Difference: Storage

On Ethereum, state variables are stored in sequential 256-bit slots. On NeoVM, they are stored in a dynamic Key-Value database where the key is deterministically generated from the variable's name (SHA256("storedData")).

Subcurrency Example

The following contract implements the simplest form of a cryptocurrency.

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.34;

import {Runtime} from "@neo/Runtime.sol";

contract Coin {
    // The keyword "public" makes variables
    // accessible from other contracts
    address public minter;
    mapping(address => uint) public balances;

    // Events allow clients to react to specific
    // contract changes you declare
    event Sent(address from, address to, uint amount);

    // Constructor code is only run when the contract
    // is created
    constructor() {
        minter = Runtime.getCallingScriptHash();
    }

    // Sends an amount of newly created coins to an address
    // Can only be called by the contract creator
    function mint(address receiver, uint amount) public {
        require(Runtime.checkWitness(minter), "Not the minter");
        balances[receiver] += amount;
    }

    // Sends an amount of existing coins
    // from any caller to an address
    function send(address receiver, uint amount) public {
        address sender = Runtime.getCallingScriptHash();
        require(Runtime.checkWitness(sender), "Not authorized");
        require(amount <= balances[sender], "Insufficient balance");
        
        balances[sender] -= amount;
        balances[receiver] += amount;
        emit Sent(sender, receiver, amount);
    }
}

This contract introduces address and mapping. The address type is a 20-byte value (a Neo script hash). The mapping type is essentially a hash table.

💡 NeoVM Difference: Authorization

Notice the use of Runtime.checkWitness(minter) instead of Ethereum's require(msg.sender == minter). Neo relies on cryptographic transaction witnesses to verify that a specific user has authorized an action, rather than tracking the immediate caller address.

Blockchain Basics

Blockchains as a concept are not too hard to understand. The reason why they are a bit difficult to wrap your head around is that they combine three concepts: cryptography, consensus, and peer-to-peer networking.

Transactions

A transaction is a message that is sent from one account to another account. It can include a binary data payload (which executes a smart contract) and a cryptographic signature (witness).

Blocks

One major problem to overcome is double spending. To solve this, transactions are grouped into blocks. On Neo N3, blocks are generated by consensus nodes using the Delegated Byzantine Fault Tolerance (dBFT) algorithm.

Unlike Ethereum's Proof-of-Stake or Proof-of-Work, dBFT provides single-block finality. Once a block is committed to the Neo blockchain, it cannot be reverted. There are no forks or reorganizations.

The Neo Virtual Machine (NeoVM)

Overview

The Neo Virtual Machine (NeoVM) is the runtime environment for smart contracts in Neo. It is not a port of the Ethereum Virtual Machine (EVM); it is a completely distinct architecture. While the neo-devpack-solidity compiler allows you to write standard Solidity, understanding the NeoVM is crucial for writing efficient and secure contracts.

Accounts

On Neo, there are two types of accounts, though they share the same 20-byte address format (Script Hash):

  1. Standard Accounts: Controlled by public-private key pairs (signatures).
  2. Contract Accounts: Controlled by the code of a deployed smart contract.

Dual Token Model (NEO and GAS)

Neo utilizes a dual-token model:

  • NEO: The governance token. It is indivisible (no decimals) and is used to vote for consensus nodes.
  • GAS: The utility token. It has 8 decimals and is used to pay for network transactions and smart contract execution.

💡 NeoVM Difference: No Native Ether

Ethereum attaches an intrinsic Ether balance to every address, manipulated via msg.value and address.transfer(). Neo does not have a native balance property. NEO and GAS are implemented as standard NEP-17 smart contracts. Value transfers require explicitly calling the GAS or NEO smart contracts.

Gas and Fees

On Neo N3, the cost of a transaction is split into two parts:

  1. System Fee (Execution Gas): Pays for the actual execution of NeoVM opcodes and syscalls.
  2. Network Fee: Pays for the byte size of the transaction and signature verification.

Storage, Memory, and the Stack

NeoVM has a different memory model than EVM:

  • Storage: A persistent Key-Value database mapping byte arrays to byte arrays. It is relatively expensive to read and write.
  • Stack: NeoVM evaluates logic using an execution stack. Items pushed to the stack are fully typed (e.g., an Integer, an Array, a Map, or a ByteArray).
  • Memory: NeoVM does not have a linear memory space. When Solidity allocates an array or struct in memory, it is actually creating a typed Array item on the NeoVM execution stack.

Message Calls and Syscalls

Contracts communicate with each other through Message Calls. Instead of arbitrary binary payloads with 4-byte selectors, NeoVM invokes methods using their exact string name via System.Contract.Call.

Furthermore, NeoVM exposes Syscalls (System Calls) which allow contracts to interact with the underlying blockchain (e.g., getting the current block, performing cryptography, or reading storage).

Delegatecall / Callcode and Libraries

There exists a special variant of a message call on Ethereum, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context (i.e. at the address) of the calling contract.

🚫 Unsupported Feature: Delegatecall and Callcode

NeoVM does not support delegatecall or callcode. Every contract has entirely isolated storage. You cannot execute another contract's code in your contract's storage context. Therefore, Ethereum-style proxy upgrade patterns and separately deployed or linkable external libraries are not available. User-defined Solidity libraries compile by inlining calls into the consuming contract.

Events and Notifications

Solidity Events are compiled to NeoVM Notifications (Runtime.Notify). Like EVM logs, these are stored in the transaction receipt and cannot be accessed from within the smart contract itself.

However, unlike EVM, Neo notifications do not utilize "Topics" for indexed parameters. All parameters are grouped into a single state array.

Create

Neo N3 contracts are deployed through the native ContractManagement.deploy() syscall with the compiled .nef bytecode and .manifest.json.

For Solidity source compatibility, new Contract(...) is accepted when the target contract is available in the compilation graph, but it does not deploy a child contract. The compiler inlines/simulates constructor-like logic and returns a zero-address placeholder. Use ContractManagement.deploy(nef, manifest, data) for real deployment.

MIT Licensed