Python compiler for the Neo Virtual Machine

The neo-boa compiler is a tool for compiling Python files to the .avm format for usage in the Neo Virtual Machine. The latter is used to execute contracts on the Neo Blockchain.

The compiler supports a subset of the Python language (in the same way that a Boa Contstrictor is a subset of the Python snake species.)

Overview

The neo-boa compiler is a tool for compiling Python files to the .avm format for usage in the Neo Virtual Machine. The latter is used to execute contracts on the Neo Blockchain.

The compiler supports a subset of the Python language (in the same way that a Boa Contstrictor is a subset of the Python snake species.)

What it currently does

  • Compiles a subset of the Python language to the .avm format for use in the Neo Virtual Machine.
  • Works for Python 3.6+
  • Support dictionary objects
  • Adds debugging map for debugging in neo-python or other NEO debuggers

What will it do

  • Compile a larger subset of the Python language.

What parts of Python are supported?

The following is a glimpse into the currently supported Python language features. For details, please see all samples in the example directory.

  • Flow Control:

    • If, Else, Elif, While, Break, Method calls, for x in y.
  • Arithmetric and equality Operators for integer math:

    • ADD, SUB, MUL, DIV, ABS, LSHIFT, RSHIFT, AND, OR, XOR, MODULO, INVERT, GT, GTE, LT, LTE, EQ, NOTEQ.
  • List creation is supported via a custom builtin. It should be noted that once created, a list length is not mutable.

    from boa.code.builtins import list
    
    # this works
    x = list(length=10)
    x[3] = 84
    
    # this also works
    x = [1,3,65,23]
    
    x.append('neo')
    
    x.remove(1)
    
    x.reverse()
    
  • list manipulation (building slices) is supported for strings and byte arrays, but not for lists

    x = [1,2,46,56]
    # this will not work
    y = x[1:3]
    
    
    x = bytearray(b'\x01\x02\x03\x04')
    
    
    # this will be bytearray(b'\x03\x04')
    k = x[1:3]
    
    # you must specify the end element of a slice, the following will not work
    z = x[1:]
    
    # instead, use this
    z = x[1:len(x)]
    
    
    # the -1 index of a list does also not work at the moment
    
  • Where possible, some of Python’s __builtins__ have been implemented in a custom way for the Neo Virtual Machine.

    from boa.code.builtins import range
    
    xrange = range(1, 30)
    
    # this also works
    for i in range(2, 21):
        i = i + 1
    

What is not supported and why?

The current Neo Virtual Machine is not as complex as your average Python interpreter. Therefore, there are many items in Python’s standard __builtins__ library that cannot be compiled to an executable smart contract. It would thus not be wise, or even possible, to import your favorite python library to do some kind of work for you. It is instread advised that you write everything you plan on using in a smart contract using the functionality listed above.

  • The __builtins__ items listed below are not supported at the moment. Many of them are not supported because the would not be supported inside the Neo Virtual Machine, while a few of these items are not supported because they just have not yet been implemented in boa.

    'zip', 'type', 'tuple', 'super', 'str', 'slice',
    
    'set', 'reversed', 'property', 'memoryview',
    
    'map', 'list', 'frozenset', 'float', 'filter',
    
    'enumerate', 'dict', 'divmod', 'complex',
    
    'bytes',  'bool', 'int', 'vars',
    
    'sum', 'sorted', 'round', 'setattr', 'getattr',
    
    'rep', 'quit', 'print', 'pow', 'ord',
    
    'oct', 'next', 'locals', 'license', 'iter',
    
    'isinstance', 'issubclass', 'input', 'id', 'hex',
    
    'help', 'hash', 'hasattr', 'globals', 'format',
    
    'exit', 'exec', 'eval', 'dir', 'deleteattr',
    
    'credits', 'copyright', 'compile', 'chr', 'callable',
    
    'bin', 'ascii', 'any', 'all'
    
  • List comprehension expressions are also not currently supported. This is on the roadmap.

    # this does NOT work
    m = [x for x in range(1,10)]
    
  • Class objects are currently not supported. Use dictionaries instead

  • Dictionaries are supported

    d = {
        'a': 10,
        'b': 4
        'j': mymethodCall(),
        'q': [1,3,5]
    }
    
    
    qlist = d['q']
    

Installation

This version of the compiler requires Python 3.6 or later

Using pip

pip install neo-boa

Manually

Clone the repository and navigate into the project directory. Make a Python 3 virtual environment and activate it via

python3 -m venv venv
source venv/bin/activate

or to explicitly install Python 3.6,

virtualenv -p /usr/local/bin/python3.6 venv
source venv/bin/activate

Then install the requirements via

pip install -r requirements.txt

Basic Usage

The compiler may be used as follows

from boa.compiler import Compiler

Compiler.load_and_save('path/to/your/file.py')

For legacy purposes, if you wish to compile without NEP8 stack isolation functionality, you may do the following:

from boa.compiler import Compiler

Compiler.load_and_save('path/to/your/file.py', use_nep=False)

See boa.compiler.Compiler and other modules for more advanced usage.

Contribute

Help

  • Open a new issue if you encounter a problem.
  • Or ping @localhuman on the NEO Slack.
  • Pull requests are welcome. New features, writing tests and documentation are all needed.

Donations

Accepted at Neo address ATEMNPSjRVvsXmaJW4ZYJBSVuJ6uR2mjQU.

License

Tests

All tests are located in boa_test/test. Tests can be run with the following command python -m unittest discover boa_test

boa.compiler.Compiler

What follows are the details of the Compiler implementation.

class boa.compiler.Compiler[source]

The main compiler interface class.

The following loads a python file, compiles it to the .avm format and saves it alongside the python file.

from boa.compiler import Compiler
Compiler.load_and_save('path/to/your/file.py')

# return the compiler object for inspection
compiler = Compiler.load('path/to/your/file.py')

# retrieve the default module for inpection
default_module = compiler.default

# retreive the default/entry method for the smart contract
entry_method = default_module.main
default

Retrieve the default or ‘entry’ module.

Returns:the default boa.code.Module object or None upon exception
static instance()[source]

Retrieve the current instance of the Compiler object, if it exists, or create one.

Returns:the singleton instance of the Compiler object
static load(path, use_nep8=True)[source]

Call load to load a Python file to be compiled but not to write to .avm

Parameters:path – the path of the Python file to compile
Returns:The instance of the compiler

The following returns the compiler object for inspection.

from boa.compiler import Compiler

compiler = Compiler.load('path/to/your/file.py')
static load_and_save(path, output_path=None, use_nep8=True)[source]

Call load_and_save to load a Python file to be compiled to the .avm format and save the result. By default, the resultant .avm file is saved along side the source file.

Parameters:
  • path – The path of the Python file to compile
  • output_path – Optional path to save the compiled .avm file
Returns:

the instance of the compiler

The following returns the compiler object for inspection

from boa.compiler import Compiler

Compiler.load_and_save('path/to/your/file.py')
write()[source]

Write the default module to a byte string.

Returns:the compiled Python program as a byte string
Return type:bytes
static write_file(data, path)[source]

Save the output data to the file system at the specified path.

Parameters:
  • data – a byte string of data to write to disk
  • path – the path to write the file to

boa.code.module.Module

What follows are the details of the Module implementation.

class boa.code.module.Module(path: str, module_name='', to_import=['*'])[source]
static ImportFromBlock(block: bytecode.cfg.BasicBlock, current_file_path)[source]
abi_entry_point = None
abi_methods = {}
actions = None
all_vm_tokens = {}
app_call_registrations = None
bc = None
blocks = None
build()[source]
cfg = None
export_abi_json(output_path)[source]

this method is used to generate a debug map for NEO debugger

export_debug(output_path)[source]

this method is used to generate a debug map for NEO debugger

extra_instructions
generate_abi_json(avm_name, file_hash)[source]
generate_avmdbgnfo(avm_name, file_hash)[source]
generate_debug_json(avm_name, file_hash)[source]
has_method(full_name)[source]
include_abi_method(method, types)[source]

Perform linkage of addresses between methods.

local_methods
main

Return the default method in this module.

Returns:the default method in this module
Return type:boa.code.method.Method
method_by_name(method_name)[source]

Look up a method by its name from the module methods list. :param method_name: the name of the method to look up :type method_name: str

Returns:the method ( if it is found)
Return type:boa.code.method.Method
methods = None
module_name = ''
orderered_methods

An ordered list of methods

Returns:A list of ordered methods is this module
Return type:list
path = None
set_abi_entry_point(method, types)[source]
to_import = None
to_s()[source]

this method is used to print the output of the executable in a readable/ tokenized format. sample usage:

>>> from boa.compiler import Compiler
>>> module = Compiler.load('./boa/tests/src/LambdaTest.py').default
>>> module.write()
>>> print(module.to_s())
12            3   LOAD_CONST          9                [data]
              4   STORE_FAST          j                [data]
22            11  LOAD_FAST           j                [data]
              17  CALL_FUNCTION       Main.<locals>.q_1                                           [<boa.code.pytoken.PyToken object at 0x10cb53c50>] [data] 22
              20  STORE_FAST          m                [data]
24            27  243                 b''      [data] 3
              30  LOAD_FAST           m                [data]
              35  NOP                                  [data]
              36  241                                  [data]
              37  242                                  [data]
              38  RETURN_VALUE                         [data]
20            49  243                 b''      [data] 3
              52  LOAD_FAST           x                [data]
              57  LOAD_CONST          1                [data]
              58  BINARY_ADD                           [data]
              59  NOP                                  [data]
              60  241                                  [data]
              61  242                                  [data]
              62  RETURN_VALUE                         [data]
write()[source]

Write the current module to a byte string.

Note that if you are using the Compiler.load('path/to/file.py'), you must call module.write() before any inspection of the module is possible.

Returns:A bytestring of representing the current module
Return type:bytes
write_methods()[source]

Write all methods in the current module to a byte string.

Returns:A bytestring of all current methods in this module
Return type:bytes

boa.code.method.Method

What follows are the details of the Method implementation.

class boa.code.method.method(module, block, module_name, extra)[source]
add_to_scope(argname)[source]
address = 0
args
block = None
blocks = []
bytecode = None
code = None
code_object = None
convert_breaks()[source]
convert_jumps()[source]
dictionary_defs = None
evaluate_annotations(index)[source]
forloop_counter
full_name
get_code_block_index(blocks)[source]
id
include_abi_info(start_index)[source]
is_abi_decorator
is_interop
module = None
module_name = None
name = None
prepare()[source]
scope
setup()[source]
stack_size = 0
stacksize
start_line_no = None
tokenizer = None
tokens = []
vm_tokens

Returns a list of all vm tokens in this method.

Returns:a list of vm tokens in this method
Return type:list

boa.code.expression.Expression

What follows are the details of the Expression implementation.

class boa.code.expression.Expression(block, tokenizer, container_method)[source]
add_method(pytoken)[source]
block = None
container_method = None
lookup_method_name(index)[source]
methodnames = None
next = None
ops = None
tokenize()[source]
tokenizer = None
updated_blocklist = None

boa.code.pytoken.PyToken

What follows are the details of the PyToken implementation.

class boa.code.pytoken.PyToken(instruction, expression, index, fallback_ln)[source]
arg_str
args
expression = None
file
func_name
index = 0
instruction = None
is_breakpoint = False
is_dynamic_appcall = False
jump_found = False
jump_from = None
jump_from_addr = None
jump_from_addr_abs
jump_target = None
jump_to_addr = None
jump_to_addr_abs
lineno
method_lineno
method_name
num_params
pyop
to_vm(tokenizer, prev_token=None)[source]
Parameters:
  • tokenizer
  • prev_token
Returns:

boa.code.vmtoken.VMToken

What follows are the details of the VMToken implementation.

class boa.code.vmtoken.VMToken(vm_op=None, pytoken=None, addr=None, data=None)[source]
addr = None
data = None
is_annotation = None
out_op
Returns:
pytoken = None
src_method = None
target_method = None
updatable_data = None
vm_op = None

Sample Python Files

The following is a selection of examples included with this project. The remainder of them can be seen at boa_test/examples. All the following items as well as other tests are tested at boa_test/tests

Addition

This example show how to add numbers. It is also an example of a smart contract that accepts multiple parameters (4).

boa_test.example.AddTest1.Main(a, b, c, d)[source]

Lists

This example shows how to create and manipulate lists.

boa_test.example.ArrayTest.Main(index)[source]
boa_test.example.ArrayTest.gethting()[source]

Binary Operators

This example shows how to use binary operators.

boa_test.example.BinopTest.Main(operation, a, b)[source]

NEP5 Token

This example shows how to create a NEP5 token.

NEP5 Standard

Author: Thomas Saunders Email: tom@neonexchange.org

Date: Dec 11 2017

boa_test.example.demo.ICO_Template.Main(operation, args)[source]
Parameters:
  • operation – str The name of the operation to perform
  • args – list A list of arguments along with the operation
Returns:

bytearray: The result of the operation

boa_test.example.demo.ICO_Template.deploy()[source]
Parameters:token – Token The token to deploy
Returns:bool: Whether the operation was successful

[Interop] Neo Blockchain

The items below are used for gathering state data contained within the blockchain. Because all items below are implemented in the Neo Virtual Machine, their source is not available here. Please see the neo-python project if you want to know more about their exact implementation.

Blockchain

boa.interop.Neo.Blockchain.GetAccount(script_hash)[source]
Parameters:script_hash
boa.interop.Neo.Blockchain.GetAsset(asset_id)[source]
Parameters:asset_id
boa.interop.Neo.Blockchain.GetBlock(height_or_hash)[source]
Parameters:height_or_hash
boa.interop.Neo.Blockchain.GetContract(script_hash)[source]
Parameters:script_hash
boa.interop.Neo.Blockchain.GetHeader(height_or_hash)[source]
Parameters:height_or_hash
boa.interop.Neo.Blockchain.GetHeight()[source]
boa.interop.Neo.Blockchain.GetTransaction(hash)[source]
Parameters:hash
boa.interop.Neo.Blockchain.GetValidators()[source]

Block

A Block object contains the transaction data for a block.

boa.interop.Neo.Block.GetTransaction(block, index)[source]

Get the transaction specified in a block

Parameters:
  • block – the block to get the transaction from
  • index – the index of the transaction within the block
boa.interop.Neo.Block.GetTransactionCount(block)[source]

Get the number of transactions in a block

Returns:the number of transactions in a block
boa.interop.Neo.Block.GetTransactions(block)[source]

Get all transactions in a block

Returns:a list of transactions contained in a block

Account

The Account object represents an address on the blockchain.

boa.interop.Neo.Account.GetBalance(account, asset_id)[source]
Parameters:
  • account
  • asset_id
boa.interop.Neo.Account.GetScriptHash(account)[source]
Parameters:account
boa.interop.Neo.Account.GetVotes(account)[source]
Parameters:account
boa.interop.Neo.Account.SetVotes(account, votes)[source]
Parameters:
  • account
  • votes

Action

An Action object is used to register an action/event listener on the blockchain.

boa.interop.Neo.Action.RegisterAction(event_name, *args)[source]
Parameters:
  • event_name
  • args

App

An App object used to call other contracts on the blockchain.

boa.interop.Neo.App.DynamicAppCall(smart_contract_hash, *args)[source]
Parameters:
  • smart_contract_hash
  • args
boa.interop.Neo.App.RegisterAppCall(smart_contract_hash, *args)[source]
Parameters:
  • smart_contract_hash
  • args

Asset

An Asset object is used to look up information about native assets such as NEO or Gas.

boa.interop.Neo.Asset.Create(asset_type, name, amount, precision, owner, admin, issuer)[source]
Parameters:
  • asset_type
  • name
  • amount
  • precision
  • owner
  • admin
  • issuer
boa.interop.Neo.Asset.GetAdmin(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.GetAmount(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.GetAssetId(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.GetAssetType(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.GetAvailable(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.GetIssuer(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.GetOwner(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.GetPrecision(asset)[source]
Parameters:asset
boa.interop.Neo.Asset.Renew(asset, years)[source]
Parameters:
  • asset
  • years

[Interop] Execution Engine

The items below are used for gathering reference about the current execution of the Neo Virtual Machine. Because all items below are implemented in the Neo Virtual Machine, their source is not available here. Please see the neo-python project if you want to know more about their exact implementation.

Methods

boa.interop.System.ExecutionEngine.GetCallingScriptHash()[source]

Get the hash of the script ( smart contract ) which began execution of the current script.

  • Note: This method is implemented inside the Neo Virtual Machine.
Returns:the hash of the script ( smart contract ) which began execution of the current script
Return type:bytearray
boa.interop.System.ExecutionEngine.GetEntryScriptHash()[source]

Get the hash of the script ( smart contract ) which began execution of the smart contract.

  • Note: This method is implemented inside the Neo Virtual Machine.
Returns:the hash of the script ( smart contract ) which began execution of the smart contract
Return type:bytearray
boa.interop.System.ExecutionEngine.GetExecutingScriptHash()[source]

Get the hash of the script ( smart contract ) which is currently being executed

  • Note: This method is implemented inside the Neo Virtual Machine.
Returns:the hash of the script ( smart contract ) which is currently being executed
Return type:bytearray
boa.interop.System.ExecutionEngine.GetScriptContainer()[source]

Return the current Script Container of a smart contract execution. This will be a boa.blockchain.vm.Neo.Transaction object.

  • Note: This method is implemented inside the Neo Virtual Machine.
Returns:the current ScriptContainer of a smart contract execution.
Return type:boa.blockchain.vm.Neo.Transaction