Steem as a Smart Contract Platform

As a blockchain developer I was excited to read the marketing material distributed about Lisk. The concept of a blockchain and smart contract platform entirely powered by JavaScript is something I have been wanting to build myself. I never got around to doing it because the complexity involved was beyond my time and budget.

The Purpose of Smart Contract Platforms

I recently reviewed the Lisk documentation to learn more about developing Smart Contracts on their platform. I was surprised to see that their approach to Smart Contracts is almost identical to what is possible with Steem today. Their first two examples were a messaging contract and a reddit contract.

I recently implemented a similar messaging application (aka plugin) for Steem. I will show a simplified version of my messaging plugin below. I simplified it by removing encryption.

Here is an example of how we would implement the messaging Smart Contract on Steem.

Step 1 - Define a message

  struct private_message {
      string from;
      string to;
      string message;
  };
  FC_REFLECT( private_message, (from)(to)(message) )

Step 2 - Define a database and indicies

This step is really no more complicated than using standard Boost Multi Index Containers. These containers have much higher performance than any SQL database with similar indices. In this case we build a robust index that makes it easy to query messages in the order they were received and/or sent. This enables "inbox" and "outbox" functionality for every user.

struct message_object {
    time_point received;
    string         from;
    string         to;
    string         message;
};
FC_REFLECT_DERIVED ( message_object, (db::object), (received)(from)(to)(message) )

struct by_to_date;
struct by_from_date;
struct by_id;  
typedef multi_index_container<
   message_object,
   indexed_by<
      ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >,
      ordered_unique< tag< by_to_date >, 
            composite_key< message_object,
               member< message_object, string, &message_object::to >,
               member< message_object, time_point, &message_object::receive_time >,
               member<object, object_id_type, &object::id >
            >,
            composite_key_compare< less<string>, 
                                   greater< time_point >, 
                                   less< object_id_type > >
      >,
      ordered_unique< tag< by_from_date >, 
            composite_key< message_object,
               member< message_object, string, &message_object::from >,
               member< message_object, time_point, &message_object::receive_time >,
               member<object, object_id_type, &object::id >
            >,
            composite_key_compare< less<string>, 
                                   greater< time_point >, 
                                   less< object_id_type > >
      >
   >
> message_multi_index_type;

typedef generic_index< message_object, message_multi_index_type> private_message_index;

Step 3 - Define an API

The purpose of defining an API is to allow 3rd parties to inspect the state of your app. In this case we need two API calls. One to fetch the inbox, and one to get the outbox.

class private_message_api : public std::enable_shared_from_this<private_message_api> {
   public:
      typedef vector<message_object> messages;
      private_message_api(const app::api_context& ctx):_app(&ctx.app){}
      
      messages get_inbox( string to, time_point newest, uint16_t limit )const;
      messages get_outbox( string from, time_point newest, uint16_t limit )const;
   private:
      app::application* _app = nullptr;
};

Step 4 - Implement Message Handler

The purpose of this method is to update state every time a private_message operation is included in the blockchain. This message handler verifies that the message was signed by the sender, and if so adds the message to the database.

void private_message_plugin::on_operation( const operation_object& op_obj ) {
  if( op_obj.op.which() == operation::tag<custom_json_operation>::value ) {
    const custom_json_operation& cop = op_obj.op.get<custom_json_operation>();
    if( cop.id == "private_message" )  {
      auto message = json::from_string(cop.json).as<private_message_operation>();
      FC_ASSERT( cop.requires_auth(message.from),"sender didn't sign message" );
      
      db.create<message_object>( [&]( message_object& pmo ) {
               pmo.from               = pm.from;
               pmo.to                 = pm.to;
               pmo.message            = pm.message;
               pmo.receive_time       = db.head_block_time();
            });
    }
  }
}

Step 5 - Implement the API

This API is accessible via JSON RPC to the steemd process. It will return up to limit messages that are older than newest where the receiver is to. The get_outbox api is almost identical except instead of using by_to_date it uses the by_from_date index.

messages private_message_api::get_inbox( string to, 
                                         time_point newest, 
                                         uint16_t limit )const 
{
   const auto& pmi = db.get_index_type<private_message_index>();
   const auto& idx = pmi.indices().get<by_to_date>();

   messages result;   
   auto itr = idx.lower_bound( make_tuple( to, newest ) );
   while( itr != idx.end() && limit && itr->to == to ) {
      result.push_back(*itr);
      ++itr;   --limit;
   }

   return result;
}

Things to Note

  1. The app developer did not have to implement any undo features.
  2. The app developer did not have to worry about fees.
  3. The app developer did not have worry about the difference between confirmed and unconfirmed.
  4. The app developer did not have to worry about serialization.
  5. The code is relatively short and concise

The Steem blockchain is powered by Graphene which automatically handles all of those complexities. Developers must still take care that their implementation does not create an infinite loop, memory leak, or generate undefined behavior.

Side Chains

A smart contract on Steem can use side chains. A side chain is really a fancy way of saying a multisig account where the transactions that should be signed are agreed upon by consensus and then signed by multiple independent parties as defined by a smart contract. Steem Smart contracts can interact with the rest of the Steem blockchain just like any other user can. The smart contract consensus algorithm will generate and sign valid Steem transactions which get included in the Steem blockchain.

App developers could use Steem and BitShares to implement smart contracts via Side Chains in the same manner. Steem has one major advantage that no other smart contract platform has: free transactions. This means that anyone can implement a side chain "smart contract plugin" without having to worry about the fees their contracts require.

Conclusion

Steem can be used as a powerful smart contract platform, the possibilities are endless.

H2
H3
H4
3 columns
2 columns
1 column
16 Comments