EOSIO Smart Contracts

EOSIO Smart Contracts

By OCI block.one Software Engineering Team

  • Phil Mesnier, Partner and Blockchain Practice Lead
    Paul Calabrese, Partner
    Kevin Heifner, Principal Software Engineer
    Brian Johnson, Principal Software Engineer
    Ciju John, Principal Software Engineer
    Jonathan Giszczak, Senior Software Engineer
    Alice Dames, Project Manager

February 2018

Introduction

What Is EOSIO?

EOSIO is a new approach to executing decentralized smart contracts on a blockchain.

EOSIO uses some of the same concepts that shaped earlier blockchains such as Graphene, the engine that powers popular sites such as SteemIt and Bitshares. Like Graphene, EOSIO is Free Open Source Software (FOSS) and released under the MIT License.

The EOSIO design recognizes the difficulty and expense related to a so-called "hard fork," where an existing blockchain must be abandoned and its contents ported to a new chain in order to enable new features or correct some major software deficiency. It is designed to avoid the necessity as much as possible.

How EOSIO Improves Upon Graphene and Other Cryptocurrencies

Blockchain-based technologies and cryptocurrencies frequently make the mainstream news because they have a number of deficiencies; the EOSIO design has features that address each of these concerns, as follows.

Issue: Account Recovery

The first issue commonly encountered with other blockchain-based technologies is the inability to reverse transactions, even in the short-term.

With EOSIO, a sophisticated and extensible permissions system makes it possible to recover an account in the event that private keys are lost or stolen.

Issue: Scaling Problems

Interchain communication among multiple instances of EOSIO allows for unbounded horizontal scaling, while also maintaining the traditional attributes of a blockchain.

Issue: Security Expenses

Another common problem with standard blockchain technologies is the sheer expense related to securing the chain using proof-of-work

Distributed proof-of-stake drastically reduces the electricity costs of running an EOSIO blockchain compared to any proof-of-work blockchain.

Existing blockchain-based, distributed application frameworks, such as Graphene, also have a number of shortcomings. The EOSIO design has features that address each of these concerns as well.

Issue: A Lack of Turing Completeness

This drawback significantly restricts the types and features of applications that can be deployed using the technology.

EOSIO achieves the Turing completeness lacking in Graphene by including deferred transactions, which can be scheduled (and rescheduled) automatically by code.

Issue: High Usage Expense

EOSIO has a sophisticated resource allocation scheme through which bandwidth, computation, and storage for distributed applications are allocated independently by staking tokens rather than paying a fee. These staked tokens are recoverable, making application usage free, and block producers are compensated through other means than per-transaction fees.

Issue: Weak Security

Other frameworks experience similar security weaknesses to those that plague other cryptocurrencies in the real world.

The previously mentioned EOSIO permissions system allows for account recovery, and together with deferred transactions, makes it possible to claw back unauthorized transactions without disrupting the integrity of the blockchain.

What Are Smart Contracts?

Unlike standard legal contracts, which are drafted by lawyers and enforced within the court system, smart contracts are made (and enforced) through cryptographic code.

Nick Szabo, a legal scholar and cryptographer, created "smart contracts" so that strangers could engage in electronic commerce safely and securely.

Decentralized ledgers manage blockchain contracts that are:

  • Stored and replicated on the system

  • Trackable and visible to all users within the blockchain

  • Supervised by the network of computers that runs the blockchain

EOSIO Features as Contracts

Many EOSIO features are implemented as contracts on their own chains. Contracts are important because they can be amended and updated within the chain itself, maintaining chain continuity while also adding new features, addressing bugs, or managing the network.

EOSIO is designed with features intended to make it easier to manage a blockchain among participants with non-chain contractual obligations to one another.

The blockchain serves as the ledger for staked tokens, and token stakeholders vote on-chain for such things as:

  • Which accounts are block producers

  • When software upgrades must be performed

  • Resource limits within the system

Use Case: A Smart Contract for a Farmers' Co-op

Here is a simple use case that will help you better understand the benefits of EOSIO smart contracts. To focus on the important interactions, we have kept both the scenario and the logic simple.

Let's say that a co-op has several farmers who have agreed to sell bushels of wheat at the same price; they create a smart contract to facilitate these transactions. Traders then come to the co-op to buy (and later resell) the wheat.

The smart contract is responsible for tracking all of the following:

  • Farmers' inventories
  • Traders' purchases
  • Fulfilment of those purchases
EOS.IO Blockchain Smart Contracts

A Sample Contract

We have provided a sample contract for you to use as a reference.

HPP File

  1. /**
  2. * @file
  3. * @copyright defined in eos/LICENSE.txt
  4. */
  5.  
  6. #include <eosiolib/eos.hpp>
  7. #include <eosiolib/token.hpp>
  8. #include <eosiolib/db.hpp>
  9. #include <eosiolib/generic_currency.hpp>
  10.  
  11. namespace coop {
  12. typedef eosio::generic_currency< eosio::token<N(coop),S(4,GOLD)> > gold;
  13.  
  14. ACTION( N(coop), addqty ) {
  15. typedef action_meta<N(coop),N(addqty)> meta;
  16. account_name farmer;
  17. uint64_t quantity;
  18.  
  19. EOSLIB_SERIALIZE( addqty, (farmer)(quantity) )
  20. };
  21.  
  22. ACTION( N(coop), purchase ) {
  23. typedef action_meta<N(coop),N(purchase)> meta;
  24. account_name trader;
  25. uint64_t quantity;
  26. eosio::asset max_price;
  27.  
  28. EOSLIB_SERIALIZE( purchase, (trader)(quantity)(max_price) )
  29. };
  30.  
  31. ACTION( N(coop), fill ) {
  32. typedef action_meta<N(coop),N(fill)> meta;
  33. account_name trader;
  34. account_name farmer;
  35. uint64_t quantity;
  36.  
  37. EOSLIB_SERIALIZE( fill, (trader)(farmer)(quantity) )
  38. };
  39.  
  40. struct farmer_data {
  41. account_name farmer;
  42. uint64_t quantity;
  43.  
  44. EOSLIB_SERIALIZE( farmer_data, (farmer)(quantity) )
  45. };
  46. typedef eosio::table64<N(coop), N(farmers), farmer_data> farmers;
  47.  
  48. struct purchase_data {
  49. account_name trader;
  50. account_name farmer;
  51. uint64_t quantity;
  52. uint64_t filled;
  53.  
  54. EOSLIB_SERIALIZE( purchase_data, (trader)(farmer)(quantity)(filled) )
  55. };
  56. typedef eosio::table64<N(coop), N(purchases), purchase_data> purchases;
  57. }

CPP File

  1. /**
  2. * @file
  3. * @copyright defined in eos/LICENSE.txt
  4. */
  5.  
  6. #include <coop/coop.hpp> /// defines transfer struct (abi)
  7. namespace coop {
  8. struct impl {
  9. static eosio::asset price() {
  10. return eosio::asset(10, S(4, GOLD));
  11. }
  12.  
  13. static void on( const addqty& act ) {
  14. require_auth( addqty::get_account() );
  15. require_auth( act.farmer );
  16. auto farmer = farmers::get_or_create( act.farmer );
  17. farmer.quantity += act.quantity;
  18. eosio::print("farmer=", act.farmer, ", quantity=", farmer.quantity, "\n");
  19. farmers::set( farmer );
  20. }
  21.  
  22. static void on( const purchase& act ) {
  23. require_auth( purchase::get_account() );
  24. require_auth( act.trader );
  25. assert (act.max_price.symbol == price().symbol, "max_price has incorrect symbol");
  26. assert (act.max_price.amount >= price().amount, "max_price below selling price");
  27.  
  28. farmer_data farmer;
  29. eosio::print("trader=", act.trader, ", quantity=", act.quantity, "\n");
  30. assert(front_i64(N(coop),N(coop),N(farmers), &farmer, sizeof(farmer_data)), "no farmers in coop");
  31. eosio::print("farmer=", farmer.farmer, ", quantity=", farmer.quantity, "\n");
  32. while (farmer.quantity < act.quantity)
  33. assert(next_i64(N(coop),N(coop),N(farmers), &farmer, sizeof(farmer_data)), "no farmers with enough quantity");
  34. eosio::print("selected farmer=", farmer.farmer, ", quantity=", farmer.quantity, "\n");
  35.  
  36. farmer.quantity -= act.quantity;
  37. farmers::set(farmer);
  38.  
  39. // simplifying code and not worrying about repeat entries
  40. auto purchase = purchases::get_or_create(act.trader);
  41. purchase.farmer = farmer.farmer;
  42. purchase.quantity = act.quantity;
  43. purchase.filled = 0;
  44. eosio::print("purchased farmer=", purchase.farmer, ", quantity=", purchase.quantity, ", trader=", act.trader, "\n");
  45. require_recipient(purchase.farmer);
  46. }
  47.  
  48. static void on( const fill& act ) {
  49. require_auth( fill::get_account() );
  50. require_auth( act.trader );
  51. auto purchase = purchases::get(act.trader);
  52. assert (!purchase.filled && purchase.farmer == act.farmer && purchase.quantity == act.quantity, "no trade found");
  53. eosio::print("purchased farmer=", purchase.farmer, ", quantity=", purchase.quantity, ", trader=", act.trader, "\n");
  54. purchase.filled = 1;
  55. purchases::set(purchase);
  56. }
  57. };
  58. }
  59.  
  60. extern "C" {
  61. /// The apply method implements the dispatch of events to this contract
  62. void apply( uint64_t code, uint64_t action ) {
  63. if (!eosio::dispatch<coop::impl, coop::addqty, coop::purchase, coop::fill>(code, action)) {
  64. if ( !eosio::dispatch<coop::gold, coop::gold::transfer_memo, coop::gold::issue>( code, action ) ) {
  65. assert( false, "received unexpected action" );
  66. }
  67. }
  68. }
  69. }

Accessing and Using the Sample Contract

Follow these three steps to access and use this contract:

  1. Review and/or clone the sample contract for this use case at the following GitHub repository: https://github.com/oci-labs/EOSContracts.git.
  2. Copy the co-op directory into your /contracts directory.
  3. You will be able to compile it if you edit /contracts/CMakeFiles.txt to add the co-op subdirectory.

How the Contract Works

When you load this contract into eos and an action (within a transaction) is sent to it, the apply function is called to process the incoming action.

Possible incoming actions include:

  • issue – adds funds to accounts
  • addqty – allows farmers to add available quantity
  • purchase – allows traders to purchase a quantity from a farmer
  • fill – allows traders to indicate that a farmer has filled their quantity

The call to dispatch will send addqty, purchase, and fill actions to their corresponding methods – with the signature "void on (const action &)".

If dispatch doesn't handle the action, the apply method will check to see if the gold currency will process the action.

Commands and Logic Behind Executing the Contract

Let's begin by executing the following code to create an eosiod node:

programs/eosio-launcher/eosio-launcher -p1 -f --eosiod "--plugin eosio::wallet_api_plugin"

Upon completion, an eosiod will be running on the localhost and listening on port 8888.

NOTE: The command in this simplified example starts an eosiod with a wallet plugin. In a real system, you:

  • Never want a wallet_api_plugin running in an eosiod, because eosioc sends private keys to the wallet.
  • Do want to have a walletd application running on the same host as your eosioc, so that private keys never leave your host.

Run the command below to create a wallet (in the eosiod) to store your private keys:

programs/eosioc/eosioc wallet create

Execute the following command, using the private key listed in tn_data_00/config.ini for private-key:

programs/eosioc/eosioc wallet import <Private Key>

Then run the following command six times:

programs/eosioc/eosioc create key

Now you have created three sets (of two keys) that are available for use.

  • The first two keys should be used for the owner and an active key for the co-op account.
  • The second two keys should be used for the owner and an active key for the farmer1 account.
  • The third two keys should be used for the owner and an active key for the trader1 account.

Run the following commands with each of the six, private keys to import them into the wallet:

programs/eosioc/eosioc wallet import <Private Key>

Use the commands below to create the accounts:

programs/eosioc/eosioc create account eosio coop <coop owner public key> <coop active public key>

programs/eosioc/eosioc create account eosio farmer1 <farmer1 owner public key> <farmer1 active public key>

programs/eosioc/eosioc create account eosio trader1 <trader1 owner public key> <trader1 active public key>

Once these commands are complete, all of the accounts exist and it's time to transfer funds from the eosio account to the co-op account:

programs/eosioc/eosioc transfer eosio coop 30000

The command below will add the contract:

programs/eosioc/eosioc set contract coop contracts/coop/coop.wast contracts/coop/coop.abi

Let's issue the initial funds for the co-op to allocate funds, providing liquidity to the contract:

programs/eosioc/eosioc push action coop issue "{\"to\":\"coop\",\"quantity\":\"20000.0000 GOLD\"}" --permission coop@active

The co-op is ready to operate and function on the open market.

The Lifecycle of a Co-Op Smart Contract Transaction

A Farmer Enters His Inventory Into the Co-op Application

EOS.IO Blockchain Smart Contracts

Along comes farmer1.

farmer1 wants to add his quantity of wheat into the co-op.

From his phone or tablet, he runs an application that will (in the background) run something similar to the command below:

programs/eosioc/eosioc push action coop addqty "{\"farmer\":\"farmer1\",\"quantity\":\"200\"}" --permission farmer1@active --permission coop@active

From his phone or tablet, farmer1 fills-in his quantity and opens up his wallet app, so the co-op's:

  • App can sign the addqty action (on a transaction).
  • Server can sign the transaction, having performed its own validation by verifying that farmer1 is a valid user with his public key signature)

The addqty action is processed in an eosiod by the impl::on(const coop::addqty& ) method in the contract.

  • The farmer's quantity is updated in the table.
  • Two signatures are required for the transaction because of the two require_auth function calls that appear at the beginning of the on method.
NOTE: Each message requires two signatures: the co-op's and whichever account is currently performing the action, e.g., a farmer or a trader.

The table contents can be verified by calling:

programs/eosioc/eosioc get table coop coop farmers

A Trader Decides to Buy Wheat

EOS.IO Blockchain Smart Contracts

Sometime later, trader1 decides he wants to participate in the co-op. The co-op authorizes $2,000 worth of funds in trader1's account:

programs/eosioc/eosioc push action coop issue "{\"to\":\"trader1\",\"quantity\":\"2000.0000 GOLD\"}" --permission coop@active

trader1 decides to purchase 200 bushels of wheat. From his phone or tablet, he runs an application that will (in the background) run something similar to the command below:

programs/eosioc/eosioc push action coop purchase "{\"trader\":\"trader1\",\"quantity\":\"200\",\"max_price\":\"10.0000 GOLD\"}" --permission trader1@active --permission coop@active

From his phone or tablet, trader1 fills-in his quantity and max price and opens up his wallet app, so the co-op's:

  • App can sign the purchase action (on a transaction)
  • Server can sign the transaction, having performed its own validation by verifying that trader1 is a valid user with his public key signature

The purchase action is processed in an eosiod by the impl::on(const coop::purchase& ) method in the contract.

The farmers table is searched for a farmer with sufficient quantity. Then the purchased table is updated with an entry listing the trader, farmer, and quantity purchased.

Two signatures are required for the transaction because of the two require_auth function calls that appear at the beginning of the on method.

The app does a:

  • Second lookup, using the previous get table command above, to look up the farmer
  • Third lookup, using the previous get table command above, to find the farmer's address (NOTE: This table is defined outside of this simplified example.)

The Purchase (Contract) Is Fulfilled

EOS.IO Blockchain Smart Contracts

trader1's trucking company proceeds to the farmer's address.

Using his application, the farmer looks up the purchase, which queries the get table for the purchases table to identify an open purchase that needs fulfillment.

The farmer loads the truck with the applicable quantity of purchased wheat.

trader1 then opens his app and enters information regarding his order, to verify the farmer has closed the open purchase.

From his phone or tablet, trader1 runs an application that will (in the background) run something similar to the command below:

programs/eosioc/eosioc push action coop fill "{\"trader\":\"trader1\",\"farmer\":\"farmer1\",\"quantity\":\"200\"}" --permission trader1@active --permission coop@active

trader1 fills in his quantity and the farmer's account and opens up his wallet app, so the co-op's:

  • App can sign the purchase action (on a transaction)
  • Server can sign the transaction, having performed its own validation by verifying that trader1 is a valid user with his public key signature

The fill action is processed in an eosiod by the impl::on(const coop::fill& ) method in the contract.

Then the purchased table is searched for the appropriate transaction and updated with an entry stating the order is filled.

Two signatures are required for the transaction because of the two require_auth function calls that appear at the beginning of the on method.

Likewise, the farmer can use his co-op app to verify that the trader has marked the purchase fulfilled. (As outlined above, behind the scenes the application will use the get table call on the purchases table.)

Finally, the farmer opens his property gate and the trader's trucking company leaves.

The sales transaction is complete.

Looking Ahead

EOSIO smart contracts have many applications and use cases, from financial transactions to healthcare to supply-chain management and beyond.

OCI is a leader in providing educational resources and consulting services for EOSIO blockchain solutions. We regularly host EOSIO Meetups (if you're in the St. Louis area, come join us!), and we offer training workshops on building distributed apps using the technology. Check out our on-demand webinar on Getting Started with EOSIO DApp Development to explore first steps.

References and Further Reading

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret