Deploying a contract

This guide describes how you can leverage aepp-sdk to compile and deploy an aeternity smart contracts.

See also

Sophia: An Æternity Blockchain Language

The Sophia is a language in the ML family. It is strongly typed and has restricted mutable state. Check out more about sophia here.

Prerequisites

  1. An account with some initial AE

  2. A smart contract written in sophia

  3. An aeternity node

  4. A sophia aehttp compiler

Assumptions

We’re going to assume that you have working knowledge of the SDK and know how to create an Account instance, a NodeClient, and a CompilerClient.

Sample Sophia Contract

Below is the sample sophia contract that we’ll use in this guide.

@compiler >= 4

contract CryptoHamster =

    record state = {
        index : int, 
        map_hamsters : map(string, hamster), 
        testvalue: int}

    record hamster = {
        id : int,
        name : string,
        dna : int}

    stateful entrypoint init() = 
        { index = 1,
            map_hamsters = {},
            testvalue = 42}
    
    public entrypoint read_test_value() : int =
        state.testvalue
    
    public entrypoint return_caller() : address =
        Call.caller

    public entrypoint cause_error() : unit =
        require(2 == 1, "require failed") 

    public stateful entrypoint add_test_value(one: int, two: int) : int =
        put(state{testvalue = one + two})
        one + two
    
    public entrypoint locally_add_two(one: int, two: int) : int =
        one + two
    
    public stateful entrypoint statefully_add_two(one: int, two: int) =
        put(state{testvalue = one + two})
    
    stateful entrypoint create_hamster(hamster_name: string) =
        require(!name_exists(hamster_name), "Name is already taken")
        let dna : int = generate_random_dna(hamster_name)
        create_hamster_by_name_dna(hamster_name, dna)

    entrypoint name_exists(name: string) : bool =
        Map.member(name, state.map_hamsters)

    entrypoint get_hamster_dna(name: string, test: option(int)) : int =
        require(name_exists(name), "There is no hamster with that name!")

        let needed_hamster : hamster = state.map_hamsters[name]

        needed_hamster.dna

    private stateful function create_hamster_by_name_dna(name: string, dna: int) =
        let new_hamster : hamster = {
            id = state.index,
            name = name,
            dna = dna}

        put(state{map_hamsters[name] = new_hamster})
        put(state{index = (state.index + 1)})

    private function generate_random_dna(name: string) : int =
        get_block_hash_bytes_as_int() - Chain.timestamp + state.index

    private function get_block_hash_bytes_as_int() : int =
        switch(Chain.block_hash(Chain.block_height - 1))
            None => abort("blockhash not found")
            Some(bytes) => Bytes.to_int(bytes)

    entrypoint test(name: string) : hash =
        String.sha3(name)

Importing required classes and methods

We need to import the following classes to use contracts.

from aeternity.node import NodeClient, Config
from aeternity.compiler import CompilerClient
from aeternity.contract_native import ContractNative
from aeternity.signing import Account

Initializing NodeClient and Compiler

Below are the steps required to initialize the NodeClient and Compiler. As you can see below, during the initialization of NodeClient we’re also providing the internal_url.

internal_url provides the debug endpoint to dry_run a contract method which can also be used to do static calls on deployed contracts and this is what exactly we’re going to use this for.

You can also not provide the internal_url but then you’ll have to disable the use of dry-run endpoint. We’ll see how to do that when we initialize our contract.

NODE_URL = os.environ.get('TEST_URL', 'http://127.0.0.1:3013')
NODE_INTERNAL_URL = os.environ.get('TEST_DEBUG_URL', 'http://127.0.0.1:3113')
COMPILER_URL = os.environ.get('TEST_COMPILER__URL', 'https://compiler.aepps.com')

node_cli = NodeClient(Config(
    external_url=NODE_URL,
    internal_url=NODE_INTERNAL_URL,
    blocking_mode=True,
))

compiler = CompilerClient(compiler_url=COMPILER_URL)

Generate an Account

You’ll need an account (using the Account class) to deploy the contract and also for stateful contract calls.

# genrate ALICE account (and transfer AE to alice account)
alice = Account.generate()

Read the Contract from file and initialize

You can read the contract from the stored .aes file and use it to initialize the contract instance. If you have not provided the internal_endpoint or simple do not want to use the dry-run functionality you can disable it by passing use-dry-run=False to the ContractNative constructor.

Warning

If you DO NOT provide the internal_url during NodeClient initialization and also DID NOT disable the dry-run then the contract method calls for un-stateful methods WILL FAIL.

CONTRACT_FILE = os.path.join(os.path.dirname(__file__), "testdata/CryptoHamster.aes")

# read contract file
with open(CONTRACT_FILE, 'r') as file:
    crypto_hamster_contract = file.read()

"""
Initialize the contract instance
Note: To disable use of dry-run endpoint add the parameter
use_dry_run=False
"""
crypto_hamster = ContractNative(client=node_cli, 
                                compiler=compiler, 
                                account=alice, 
                                source=crypto_hamster_contract)

Compile and deploy the contract

You can compile the contract and deploy it using the deploy method. If your init method accepts any arguments then please provide them inside the deploy method. Once the contract is compiled and deployed, the signed transaction is returned.

# deploy the contract 
# (also pass the args of thr init function - if any)
tx = crypto_hamster.deploy()
print(f"contract address: {crypto_hamster.address}")

Thats all, you have successfully deployed a smart contract in Aeternity, the next tutorial will guide you to call the contract