Calling smart contracts

Currently, there are 2 different ways to call another smart contract on the blockchain: using a call_contract method or using an interface, but internally they work both the same way.

Let us suppose the following contract was deployed on Neo:

from boa3.builtin.compile_time import public


@public
def hello_stranger(name: str) -> str:               
    return "Hello " + name

To call the hello_stranger method, first you’ll need to know the smart contract’s script hash, Let us also assume it is 0x000102030405060708090A0B0C0D0E0F10111213.

with call_contract

Use the call_contract method from boa3.builtin.interop.contract on your smart contract. You’ll need to use the script hash, followed by the name of the function you want to call, followed by the arguments of said function. You’ll also need to type cast the return so the compiler type checker works as expected, otherwise the return type will be considered as Any.

# calling_with_call_contract.py
from boa3.builtin.compile_time import public
from boa3.builtin.interop.contract import call_contract
from boa3.builtin.type import UInt160

@public
def calling_other_contract() -> str:
    greetings: str = call_contract(UInt160(b'\x13\x12\x11\x10\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00'),     # usually, script hashes that starts with "0x" means that they are using big endian, so when using `bytes` you'll need to revert the order
                                   'hello_stranger',     # it's the function's name
                                   'John Doe'            # the parameter of 'hello_stranger'
                                  )
    return greetings

Note: If you are going to call a contract only once, then it’s ok to use call_contract, however, it might be hard to keep track of a lot of call_contracts on the same file. It’s pretty much always better to use an interface when dealing with other smart contracts.

with Interface

Use the contract decorator from boa3.builtin.compile_time using the script hash and create a class that have the same methods you want call.

# calling_with_interface.py
from boa3.builtin.compile_time import contract, public
from boa3.builtin.type import UInt160

@public
def calling_other_contract() -> str:
    greetings = HelloStrangerContract.hello_stranger('John Doe')
    return greetings


@contract('0x000102030405060708090A0B0C0D0E0F10111213')
class HelloStrangerContract:
    hash: UInt160   # this class variable will reflect the value you passed to the `contract` decorator
    
    @staticmethod
    def hello_stranger(name: str) -> str:
        pass

Calling native contracts

Neo3-Boa already has interfaces for all the native contracts that you can import from boa3.builtin.nativecontract

# calling_native_contract.py
from boa3.builtin.compile_time import public
from boa3.builtin.nativecontract.neo import NEO

@public
def calling_other_contract() -> str:
    neo_symbol = NEO.symbol()
    return neo_symbol

Automate with CPM

Instead of manually writing the smart contract interface, you can use CPM to generate it automatically. After installing Neo3-Boa, you can install CPM by typing install_cpm on CLI (without the neo3-boa prefix). Then, you’ll need to create a cpm.yaml config file, put the smart contract information there, and run cpm.

For example, if you use CPM to create a dice smart contract interface, the following file will be generated:

# cpm_out/python/dice/contract.py
from boa3.builtin.type import UInt160, UInt256, ECPoint
from boa3.builtin.compile_time import contract, display_name
from typing import cast, Any


@contract('0x4380f2c1de98bb267d3ea821897ec571a04fe3e0')
class Dice:
    hash: UInt160

    @staticmethod
    def rand_between(start: int, end: int) -> int: 
        pass

    @staticmethod
    def map_bytes_onto_range(start: int, end: int, entropy: bytes) -> int: 
        pass

    @staticmethod
    def roll_die(die: str) -> int: 
        pass

    @staticmethod
    def roll_dice_with_entropy(die: str, precision: int, entropy: bytes) -> list: 
        pass

    @staticmethod
    def update(script: bytes, manifest: bytes, data: Any) -> None: 
        pass

Then, all you need to do is import this class onto your smart contract.

# calling_with_cpm.py
from boa3.builtin.compile_time import public
from cpm_out.python.dice.contract import Dice


@public
def calling_other_contract() -> str:
    d6_roll = Dice.rand_between(1, 6)
    return "Dice result is " + str(d6_roll)