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

The prompt should change to >>> signifying that it’s ready for Python commands.

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:

import eosf
contract = eosf.ContractBuilderFromTemplate("hello.world", template="skeleton")

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 only two templates (skeleton and eosio.token) but there’ll be more in the future. This parameter is optional, the default value is skeleton.

The above command creates a new contract. However, if you want to access an existing one, use the following syntax instead:

contract = eosf.ContractBuilder("hello.world")

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 hello.world.cpp file in your favourite text editor by commenting out line 17, 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( "user: ", 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:

temp = contract.build_abi()
temp = contract.build_wast()

Or you can generate both at the same time:

contract.build()

NOTE: The build_abi() and build() methods are relying on the EOSIO ABI compiler which has an experimental status in the EOSIO repository. Thus the ABI output might be different than expected. For the time being we recommend using the build_wast() method only and creating the ABI file manually.

Deploy the contract

First, start the testnet and initialize the workspace:

import node, sess
node.reset()
sess.init()

Then create an account which will be holding the contract:

import eosf
account = eosf.account(sess.eosio)
sess.wallet.import_key(account)

Next, let’s redefine the contract, so that it’s associated with the above account and thus becomes deployable:

contract = eosf.Contract(account, contract.path())

Or you can use the contract name explicitly:

contract = eosf.Contract(account, "hello.world")

And now we can deploy the contract:

contract.deploy()

Test the contract

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

contract.push_action("hi", '{"user":"' + str(sess.alice) + '"}', sess.alice, output=True)
contract.push_action("hi", '{"user":"' + str(sess.carol) + '"}', sess.alice, output=True)

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

NOTE: When the output flag in the push_action() method is set to True, the output from the contract is being displayed in the console, for example:

INFO user: alice @ 17:24:7 hello.world55.cpp[16](hi)
Hello, alice

If you want the output stream produced by nodeos to be displayed instead, set this flag to False and then you should see an output similar to this:

w3qdegfsbago <= w3qdegfsbago::hi {"user":"carol"}
executed transaction: 8c3cf0b611d0d8387ae337c8b...
warning: transaction executed locally, but may not be confirmed by the network yet

Modify the code, re-compile & re-deploy

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

void hi( account_name user ) {
    logger_info( "user: ", 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":"' + str(sess.carol) + '"}', sess.alice, output=True)
Error 3090004: missing required authority

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

contract.push_action("hi", '{"user":"' + str(sess.carol) + '"}', sess.carol, output=True)

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:

node.stop()

To exit Python CLI:

exit()

Alternatively, use the ctrl-D shortcut.