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 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:

contract = ContractBuilder(project_from_template("foo_bar", template="01_hello_world"))

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. 01_hello_world, 02_eosio_token and 03_tic_tac_toe), with more coming in the future. This parameter is optional, the default value is 01_hello_world.

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 instead:

contract = ContractBuilder("foo_bar")

NOTE: In a similar way you could access a demo contract supplied by EOSFactory, for example:

contract = ContractBuilder("01_hello_world")

Or a demo contract supplied by EOSIO, for example:

contract = ContractBuilder("eosio.token")

Edit the source code

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

contract.path()

Locate the folder containing the new contract (if you’re not sure where it is, use the output produced by the contract.path method) and edit the foo_bar.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 "logger.hpp"
#include "hello.world.hpp"

using namespace eosio;

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

    /// @abi 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.build_abi()
contract.build_wast()

Or you can generate both at the same time:

contract.build()

Deploy the contract

First, start the testnet and initialize the workspace:

reset()
create_wallet()
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:

contract = Contract(host, contract.path())

Or you can use the contract name explicitly:

contract = Contract(host, "foo_bar")

And now we can deploy the contract:

contract.deploy()

NOTE: In a similar way you could deploy a demo contract supplied by EOSFactory, for example:

contract = Contract(host, "01_hello_world")
contract.deploy()

Or a demo contract supplied by EOSIO, for example:

contract = Contract(host, "eosio.token")
contract.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.push_action("hi", {"user":alice}, permission=alice)
contract.push_action("hi", {"user":carol}, permission=carol)
contract.push_action("hi", {"user":alice}, permission=carol)
contract.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.push_action("hi", {"user":alice}, alice)
contract.push_action("hi", {"user":alice}, permission=alice)
contract.push_action("hi", {"user":alice}, permission=(alice, Permission.ACTIVE))
contract.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_bar.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.build()

And re-deploy the contract:

contract.deploy()

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

contract.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.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.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.