Building and Deploying EOS Contracts using EOSFactory

The purpose of this tutorial is to demonstrate how you can use EOSFactory to execute the simplest development cycle: create a new contract, edit the code, build the contract, deploy it and interact with it.

Prerequisites

Run Python CLI in VSC

Open a bash terminal and run Python CLI:

python3

Once in the Python shell, import the EOSFactory library:

from eosfactory.eosf import *

Create a new contract from template

To create a new contract from a pre-defined template all you need is a name for the contract and the name of the template, for example:

project_path = project_from_template("foo", template="hello_world", remove_existing=True)
NOTE: Do not use spaces in contract names. What is allowed are letters, numbers, underscores _, dots . and dashes -. Regarding the second parameter, as of now there are three templates to choose from (i.e. hello_world, eosio_token and tic_tac_toe), with more coming in the future. This parameter is optional, the default value is hello_world.

Create reference to an existing contract

The above command creates a new folder and inside it a new smart-contract file is placed. However, if you want to access an existing smart-contract, use the following syntax and specifying the entire path, for example:

contract_builder_foo = ContractBuilder(project_path)

Or use just the folder name, if the contract is located in the workspace you explicitly defined when installing EOSFactory:

contract_builder_foo = ContractBuilder("foo")

In a similar way, you can access demo contracts shipped with EOSFactory, for example:

contract_builder_hello = ContractBuilder("hello_world")

Edit the source code

To check the directory where the contract’s files are located:

print(contract_builder_foo.path())

Locate the folder containing the new contract (if you’re not sure where it is, use the output produced by the contract_builder_foo.path method) and edit the foo.cpp file in your favorite text editor by commenting out line 18, i.e. require_auth( user ):

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

#define DEBUG

#include <eoside/logger.hpp>
#include "hello.world.hpp"

using namespace eosio;

class hello : public eosio::contract {
  public:
    using contract::contract;

    [[eosio::action]]
    void hi( account_name user ) {
      logger_info( "debug user name: ", name{user} );
      //require_auth( user );
      print( "Hello, ", name{user} );
    }
};

EOSIO_ABI( hello, (hi) )

Build the new contract

You can generate ABI and the web assembly code separately:

contract_builder_foo.build_abi()
contract_builder_foo.build_wast()

Or you can generate both at the same time:

contract_builder_foo.build()

Deploy the contract

First, start the testnet and initialize the workspace:

reset()
create_master_account("master")

Then create an account which will be holding the contract:

create_account("host", master)

Next, let’s redefine the contract, so that it’s associated with the above account and thus becomes deployable. You can use the entire path to the contract’s folder:

contract_foo = Contract(host, contract_builder_foo.path())

Or you can use the name of the contract’s folder (provided it’s located in your smart-contract workspace):

contract_foo = Contract(host, "foo")

Next, we can deploy the contract:

contract_foo.deploy()
NOTE: In a similar way you could deploy a demo contract supplied by EOSFactory, for example:
contract_hello = Contract(host, "hello_world")
contract_hello.build()
contract_hello.deploy()

Test the contract

First, let’s create a couple of testing accounts:

create_account("alice", master)
create_account("carol", master)

You can play with the contract by sending it actions with different arguments:

contract_foo.push_action("hi", {"user":alice}, permission=alice)
contract_foo.push_action("hi", {"user":carol}, permission=carol)
contract_foo.push_action("hi", {"user":alice}, permission=carol)
contract_foo.push_action("hi", {"user":carol}, permission=alice)

NOTE: The push_action method takes three parameters:

  • the name of the action, e.g. "hi",
  • the data required by the action, e.g. {"user":alice},
  • the permissions required by the action, e.g. alice.

Regarding permissions, EOSFactory offers several options:

contract_foo.push_action("hi", {"user":alice}, alice)
contract_foo.push_action("hi", {"user":alice}, permission=alice)
contract_foo.push_action("hi", {"user":alice}, permission=(alice, Permission.ACTIVE))
contract_foo.push_action("hi", {"user":alice}, permission=[(alice, Permission.ACTIVE), (carol, Permission.OWNER)])

All the above variations should work, as the contract allows anyone to authorize the hi action.

NOTE: Among other things, the console displays a debugger output, for example:
INFO debug user name: alice @ 12:26:05 hello.world.cpp[17](hi)

This is the result of a logger_info clause in the foo.cpp file (line 17):

logger_info( "debug user name: ", name{user} )

Modify the code, re-compile & re-deploy

And now let’s modify the hi method by uncommenting line 18, so that that contract authenticates the user before further execution:

void hi( account_name user ) {
  logger_info( "debug user name: ", name{user} );
  require_auth( user );
  print( "Hello, ", name{user} );
}

Re-compile the contract:

contract_foo.build()

And re-deploy the contract:

contract_foo.deploy()

Now, if we attempt to mismatch the user and the authority, the contract will throw an error:

contract_foo.push_action("hi", {"user":alice}, permission=carol)
Error 3090004: Missing required authority
Ensure that you have the related authority inside your transaction!

But if we use the appropriate authority, there should no error:

contract_foo.push_action("hi", {"user":alice}, permission=alice)

Clean up

When your are done your contract, you might want to delete it from your workspace:

contract_foo.delete()
NOTE: The above command removes the entire folder.

To stop the testnet:

stop()

To exit Python CLI:

exit()

Alternatively, use the ctrl-D shortcut.