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
A Sample Contract
We have provided a sample contract for you to use as a reference.
- /**
- * @file
- * @copyright defined in eos/LICENSE.txt
- */
-
- #include <eosiolib/eos.hpp>
- #include <eosiolib/token.hpp>
- #include <eosiolib/db.hpp>
- #include <eosiolib/generic_currency.hpp>
-
- namespace coop {
- typedef eosio::generic_currency< eosio::token<N(coop),S(4,GOLD)> > gold;
-
- ACTION( N(coop), addqty ) {
- typedef action_meta<N(coop),N(addqty)> meta;
- account_name farmer;
- uint64_t quantity;
-
- EOSLIB_SERIALIZE( addqty, (farmer)(quantity) )
- };
-
- ACTION( N(coop), purchase ) {
- typedef action_meta<N(coop),N(purchase)> meta;
- account_name trader;
- uint64_t quantity;
- eosio::asset max_price;
-
- EOSLIB_SERIALIZE( purchase, (trader)(quantity)(max_price) )
- };
-
- ACTION( N(coop), fill ) {
- typedef action_meta<N(coop),N(fill)> meta;
- account_name trader;
- account_name farmer;
- uint64_t quantity;
-
- EOSLIB_SERIALIZE( fill, (trader)(farmer)(quantity) )
- };
-
- struct farmer_data {
- account_name farmer;
- uint64_t quantity;
-
- EOSLIB_SERIALIZE( farmer_data, (farmer)(quantity) )
- };
- typedef eosio::table64<N(coop), N(farmers), farmer_data> farmers;
-
- struct purchase_data {
- account_name trader;
- account_name farmer;
- uint64_t quantity;
- uint64_t filled;
-
- EOSLIB_SERIALIZE( purchase_data, (trader)(farmer)(quantity)(filled) )
- };
- typedef eosio::table64<N(coop), N(purchases), purchase_data> purchases;
- }
- /**
- * @file
- * @copyright defined in eos/LICENSE.txt
- */
-
- #include <coop/coop.hpp> /// defines transfer struct (abi)
- namespace coop {
- struct impl {
- static eosio::asset price() {
- return eosio::asset(10, S(4, GOLD));
- }
-
- static void on( const addqty& act ) {
- require_auth( addqty::get_account() );
- require_auth( act.farmer );
- auto farmer = farmers::get_or_create( act.farmer );
- farmer.quantity += act.quantity;
- eosio::print("farmer=", act.farmer, ", quantity=", farmer.quantity, "\n");
- farmers::set( farmer );
- }
-
- static void on( const purchase& act ) {
- require_auth( purchase::get_account() );
- require_auth( act.trader );
- assert (act.max_price.symbol == price().symbol, "max_price has incorrect symbol");
- assert (act.max_price.amount >= price().amount, "max_price below selling price");
-
- farmer_data farmer;
- eosio::print("trader=", act.trader, ", quantity=", act.quantity, "\n");
- assert(front_i64(N(coop),N(coop),N(farmers), &farmer, sizeof(farmer_data)), "no farmers in coop");
- eosio::print("farmer=", farmer.farmer, ", quantity=", farmer.quantity, "\n");
- while (farmer.quantity < act.quantity)
- assert(next_i64(N(coop),N(coop),N(farmers), &farmer, sizeof(farmer_data)), "no farmers with enough quantity");
- eosio::print("selected farmer=", farmer.farmer, ", quantity=", farmer.quantity, "\n");
-
- farmer.quantity -= act.quantity;
- farmers::set(farmer);
-
- // simplifying code and not worrying about repeat entries
- auto purchase = purchases::get_or_create(act.trader);
- purchase.farmer = farmer.farmer;
- purchase.quantity = act.quantity;
- purchase.filled = 0;
- eosio::print("purchased farmer=", purchase.farmer, ", quantity=", purchase.quantity, ", trader=", act.trader, "\n");
- require_recipient(purchase.farmer);
- }
-
- static void on( const fill& act ) {
- require_auth( fill::get_account() );
- require_auth( act.trader );
- auto purchase = purchases::get(act.trader);
- assert (!purchase.filled && purchase.farmer == act.farmer && purchase.quantity == act.quantity, "no trade found");
- eosio::print("purchased farmer=", purchase.farmer, ", quantity=", purchase.quantity, ", trader=", act.trader, "\n");
- purchase.filled = 1;
- purchases::set(purchase);
- }
- };
- }
-
- extern "C" {
- /// The apply method implements the dispatch of events to this contract
- void apply( uint64_t code, uint64_t action ) {
- if (!eosio::dispatch<coop::impl, coop::addqty, coop::purchase, coop::fill>(code, action)) {
- if ( !eosio::dispatch<coop::gold, coop::gold::transfer_memo, coop::gold::issue>( code, action ) ) {
- assert( false, "received unexpected action" );
- }
- }
- }
- }
Accessing and Using the Sample Contract
Follow these three steps to access and use this contract:
- Review and/or clone the sample contract for this use case at the following GitHub repository: https://github.com/oci-labs/EOSContracts.git.
- Copy the co-op directory into your
/contracts
directory. - 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 accountsaddqty
– allows farmers to add available quantitypurchase
– allows traders to purchase a quantity from a farmerfill
– 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 aneosiod
with a wallet plugin. In a real system, you:
- Never want a
wallet_api_plugin
running in aneosiod
, becauseeosioc
sends private keys to the wallet.- Do want to have a
walletd
application running on the same host as youreosioc
, 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
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 theon
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
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
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
- https://eos.io/
- EOS.IO Github repository
- Smart Contracts: The Blockchain Technology That Will Replace Lawyers
- Nick_Szabo Wikipedia Entry
- Szabo, Nick (September 1997). "Formalizing and Securing Relationships on Public Networks." First Monday.
- SETT March 2017: Graphene – An Open Source Blockchain
- Blockchain for Non-techies: 3. Smart Contracts
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.