EOS - Example Exchange Contract and Benefits of C++

in #eos7 years ago

This week I have been focused on the API that smart contract developers will use to write contracts. To help facilitate the design of this API I have given myself an example contract to write. This time the example is a little bit more complex than just a currency contract, but a full up exchange between the native EOS currency and an example CURRENCY contract.

Benefits of C++ API

Developers building on EOS will write their smart contracts in C++ that gets compiled to Web Assembly and then published to the blockchain. This means that we can take advantage of C++'s type and template system to ensure our contracts are safe.

One of the most basic kinds of safety is known as dimensional analysis, aka keeping your units straight. When developing an exchange you are dealing with several numbers with different units: EOS, CURRENCY, and EOS / CURRENCY.

A simple implementation would do something like this:

struct account {
  uint64_t  eos_balance;
  uint64_t  currency_balance;
};

The problem with this simple approach is that the following code could be accidentally written:

void buy( Bid order ) {
   ...
   buyer_account.currency _balance  -=  order.quantity;
   ...
}

At first glance the error isn't obvious, but upon closer inspection bids consume the eos_balance rather than the currency_balance. In this case the market is setup to price CURRENCY in EOS. Another kind of error that could occur is the following:

   auto receive_tokens = order.quantity * order.price;

This particular line of code may be valid if order is a Bid, but the price would need to be inverted if it were an Ask. As you can see without proper dimensional analysis there is no way to be certain you are adding apples to apples and not apples to oranges.

Fortunately, C++ allows us to use templates and operator overloading to define a runtime cost-free validation of our units.

template<typename NumberType, uint64_t CurrencyType = N(eos) >
   struct token {
       token(){}
       explicit token( NumberType v ):quantity(v){};
       NumberType quantity = 0;

       token& operator-=( const token& a ) {
          assert( quantity >= a.quantity, 
                        "integer underflow subtracting token balance" );
          quantity -= a.quantity;
          return *this;
       }

       token& operator+=( const token& a ) {
          assert( quantity + a.quantity >= a.quantity, 
                       "integer overflow adding token balance" );
          quantity += a.quantity;
          return *this;
       }

       inline friend token operator+( const token& a, const token& b ) {
          token result = a;
          result += b;
          return result;
       }

       inline friend token operator-( const token& a, const token& b ) {
          token result = a;
          result -= b;
          return result;
       }

       explicit operator bool()const { return quantity != 0; }
   };

With this definition there is now a clear type distinction in the account:

struct Account {
   eos::Tokens               eos_balance;
   currency::Tokens    currency_balance;
};

struct Bid {
    eos::Tokens quantity;
};

With this in place the following will generate a compile error because there is no -= operator defined for eos::Tokens and currency::Tokens.

void buy( Bid order ) {
   ...
   buyer_account.currency _balance  -=  order.quantity;
   ...
}

Using this technique I was able to use the compiler to identify and fix many unit mismatches in my implementation of an example exchange contract. The really nice thing about all of this is that the final web assembly generated by the C++ compiler is identical to what would have been generated if I had simply used uint64_t for all of my balances.

Another thing you may notice is that the token class also automatically checks for over and underflow exceptions.

Simplified Currency Contract

In the process of writing the exchange contract I first had update the currency contract. In doing so I refactored the currency contract into a header currency.hpp and a source currency.cpp so that the exchange contract could access types defined by the currency contract.

currency.hpp

#include <eoslib/eos.hpp>
#include <eoslib/token.hpp>
#include <eoslib/db.hpp>

/**
 * Make it easy to change the account name the currency is deployed to.
 */
#ifndef TOKEN_NAME
#define TOKEN_NAME currency
#endif

namespace TOKEN_NAME {

   typedef eos::token<uint64_t,N(currency)> Tokens;

   /**
    *  Transfer requires that the sender and receiver be the first two
    *  accounts notified and that the sender has provided authorization.
    */
   struct Transfer {
      AccountName       from;
      AccountName       to;
      Tokens            quantity;
   };

   struct Account {
      Tokens           balance;

      bool  isEmpty()const  { return balance.quantity == 0; }
   };

   /**
    *  Accounts information for owner is stored:
    *
    *  owner/TOKEN_NAME/account/account -> Account
    *
    *  This API is made available for 3rd parties wanting read access to
    *  the users balance. If the account doesn't exist a default constructed
    *  account will be returned.
    */
   inline Account getAccount( AccountName owner ) {
      Account account;
      ///      scope, code, table,      key,       value
      Db::get( owner, N(currency), N(account), N(account), account );
      return account;
   }

} /// namespace TOKEN_NAME

currency.cpp

#include <currency/currency.hpp> /// defines transfer struct (abi)

namespace TOKEN_NAME {

   ///  When storing accounts, check for empty balance and remove account
   void storeAccount( AccountName account, const Account& a ) {
      if( a.isEmpty() ) {
         printi(account);
         ///        scope    table       key
         Db::remove( account, N(account), N(account) );
      } else {
         ///        scope    table       key         value
         Db::store( account, N(account), N(account), a );
      }
   }

   void apply_currency_transfer( const TOKEN_NAME::Transfer& transfer ) {
      requireNotice( transfer.to, transfer.from );
      requireAuth( transfer.from );

      auto from = getAccount( transfer.from );
      auto to   = getAccount( transfer.to );

      from.balance -= transfer.quantity; /// token subtraction has underflow assertion
      to.balance   += transfer.quantity; /// token addition has overflow assertion

      storeAccount( transfer.from, from );
      storeAccount( transfer.to, to );
   }

}  // namespace TOKEN_NAME

Introducing the Exchange Contract

The exchange contract processes the currency::Transfer and the eos::Transfer messages when ever the exchange is the sender or receiver. It also implements three of its own messages: buy, sell, and cancel. The exchange contract defines its public interface in exchange.hpp which is where the message types and database tables are defined.

exchange.hpp

#include <currency/currency.hpp>

namespace exchange {

   struct OrderID {
      AccountName name    = 0;
      uint64_t    number  = 0;
   };

   typedef eos::price<eos::Tokens,currency::Tokens>     Price;

   struct Bid {
      OrderID            buyer;
      Price              price;
      eos::Tokens        quantity;
      Time               expiration;
   };

   struct Ask {
      OrderID            seller;
      Price              price;
      currency::Tokens   quantity;
      Time               expiration;
   };

   struct Account {
      Account( AccountName o = AccountName() ):owner(o){}

      AccountName        owner;
      eos::Tokens        eos_balance;
      currency::Tokens   currency_balance;
      uint32_t           open_orders = 0;

      bool isEmpty()const { return ! ( bool(eos_balance) | bool(currency_balance) | open_orders); }
   };

   Account getAccount( AccountName owner ) {
      Account account(owner);
      Db::get( N(exchange), N(exchange), N(account), owner, account );
      return account;
   }

   TABLE2(Bids,exchange,exchange,bids,Bid,BidsById,OrderID,BidsByPrice,Price);
   TABLE2(Asks,exchange,exchange,bids,Ask,AsksById,OrderID,AsksByPrice,Price);


   struct BuyOrder : public Bid  { uint8_t fill_or_kill = false; };
   struct SellOrder : public Ask { uint8_t fill_or_kill = false; };
}

The exchange contract source code gets a bit long for this post, but you can view it on github. For now I will show the core message handler for a SellOrder to get an idea how it would be implemented:

void apply_exchange_sell( SellOrder order ) {
   Ask& ask = order;
   requireAuth( ask.seller.name );

   assert( ask.quantity > currency::Tokens(0), "invalid quantity" );
   assert( ask.expiration > now(), "order expired" );

   static Ask existing_ask;
   assert( AsksById::get( ask.seller, existing_ask ), "order with this id already exists" );

   auto seller_account = getAccount( ask.seller.name );
   seller_account.currency_balance -= ask.quantity;

   static Bid highest_bid;
   if( !BidsByPrice::back( highest_bid ) ) {
      assert( !order.fill_or_kill, "order not completely filled" );
      Asks::store( ask );
      save( seller_account );
      return;
   }

   auto buyer_account = getAccount( highest_bid.buyer.name );

   while( highest_bid.price >= ask.price ) {
      match( highest_bid, buyer_account, ask, seller_account );

      if( highest_bid.quantity == eos::Tokens(0) ) {
         save( seller_account );
         save( buyer_account );
         Bids::remove( highest_bid );
         if( !BidsByPrice::back( highest_bid ) ) {
            break;
         }
         buyer_account = getAccount( highest_bid.buyer.name );
      } else {
         break; // buyer's bid should be filled
      }
   }

   save( seller_account );
   if( ask.quantity ) {
      assert( !order.fill_or_kill, "order not completely filled" );
      Asks::store( ask );
   }
}

As you can see the code here is relatively concise, readable, safe, and most importantly performant.

Isn't C++ an Unsafe Language?

Those who are into language wars may be familiar with the challenges that C and C++ programers have managing memory. Fortunately, most of those issues go away when building smart contracts because your program "restarts" with a clean slate at the start of every message. Furthermore, there is rarely a need to implement dynamic memory allocation. This entire exchange contract doesn't call new or delete nor malloc or free. The WebAssembly framework will automatically reject any transaction that would address memory wrong.

This means that most of the problems with C++ go away when using it for short-lived message handlers and we are left with its many benefits.

Conclusion

EOS.IO software is progressing nicely and I couldn't be happier with how much fun it is to write smart contracts using this API.

Sort:  
There are 3 pages
Pages

Nice post! Steemit really needs syntax highlighting though :) Any thoughts on using something like AssemblyScript for writing contracts?

Great Suggestion

Can you compare with TurboScript? Looks like AssemblyScript is much more mature than TurboScript. I wonder though why we have two similar projects.

Does it decompile from wasm too?

AFAIK AssemblyScript was born out of dissatisfaction with some of the design choices in TurboScript. Not sure what the exact differences are anymore, looks like TurboScript is moving to binaryen now as well. I would personally go with AssemblyScript in a heartbeat just because I know that the dcode guy makes some great modules.

It doesn't decompile from wasm, and I don't think it ever will. You could maybe use SourceMaps to go back when support for that is added.

I spent some time learning Wren. Now I'll spend some time learning C++. I've done things in many different languages, but never C or C++. I'm excited for the journey, but also a bit overwhelmed thinking how far I have to go to get to where I'd someday like to be. Thank you for these examples.

Can you give us an idea when a testnet will be up so noobie muggles like myself can start playing around with this stuff?

C was my favorite for a long time, then I moved onto C++ but it still allowed me to do C so I didn't really benefit from C++ as much as I should have since it didn't FORCE me to learn the new things. More recently I've mostly been using C# which is similar in many ways but alien in others. :)

Hey you guys both inspired me. I'm reading your comments thinking, "Damn, I haven't tried programming anything in C or C++ since the early 2000s." I want to be able to make apps and develop ideas in modern ways. I've just been telling myself it's too hard or I'm never going to master it or ever even get close. That's limiting talk and I'm thinking now that I can make programs and learn to code if that's what I really want.

Very good "documentation", thanks @dan

@dan ... hard work there!! I know something about... Thank You!!

Oooo really very interesting post you I am very amazed to see him congratulations on the achievement that you can not all this apart from the results of your hard work in esteem I also want as you want kah you follow me and upvote me and share my post so that your friends can Even though not as good as your post if you want me very grateful to you Congratulations, hopefully sukse always make you up to the front

I don't code but somehow I find these post.... calming and exciting at the same time :-)

As I probably wouldn't write my own smart contracts but just use EOS (I wish it lots of success as the things I have read about it sound great) I don't mind if it is C++. Coming from a web development background and seeing the rapid adoption of JavaScript / NodeJS (Go too) for all kinds of things I think it's a bit of a pity that all those developers able to code would have a steep (ok maybe not steep but still) learning curve writing a contract using C++.

@dan Would there be the possibility to define smart contracts in ES6 and then (somehow) convert / compile them to C++?

JavaScript doesn't have declarative syntax like in C++. there is no all let's make this a variable an integer or this variable something else, whereas C++ does so. C++ also has classes and operator overloading whereas Javascript does not.

It seems to me C++ should be the soirce and JS sometimes the target. Although sometimes reading erors related to templates can be like reading Greek.

Good post thanks for sharing

This comment has received a 0.15 % upvote from @booster thanks to: @hamzaoui.

I don't know C++ but your explanation makes a lot of sense. Glad to hear about the progress on EOS.

Hi Dan. I am interested in the EOS project, and I wanted to ask you if you would like me to translate some of your post for the Spanish-speaking community, since I do not see a post on this topic and I think that in my community most people do not know EOS.

I am not a professional translator, but I would try to do my best.
I hope you have a good Friday!

Smart contracts is definitely the way forward into the future. EOS has great potential and can possibly one of the greatest in it's space. I'll be keeping an eye out.
@incomepal

I don’t know how to code and most of this is like reading a foreign language but I’m happy with your conclusion that EOS is progressing nicely :). Thanks for the post.

I wish eos a great long life ahead
Keep it up

cool :) i can see this as an asp or php thanks!

Thanks for the review!

Cool!!

Thanks for taking the time to explain everything well detailed and clear. @dan

EOS's huge ICO caused a big market hype jump for them and the Buzz is over, not sure if it will rise again in my opinion. Tezos ICO same thing, now all ICO will think about 200m plus and it won't happen everytime.

Last paragraph caught my attention, since no new or delete is called, using c++ is safe but there is nothing preventing from anyone writing contract to do so?How do you enforce?or are you saying if they use web assembly framework will some how detect it? but still it is run time only? may be this is best practice to writing "EOS contracts"

Before I start, I've not looked in-depth at how EOS smart contracts will be implemented. Consider this a thinking out loud comment.

Furthermore, there is rarely a need to implement dynamic memory allocation.

What about developers that are intentionally trying to cause harm? You mention that it restarts with a clean slate each time, but what does that mean?

@dan
Given that (as you mention) c++ is inherently unsafe, what protection is there against obfuscated contracts that cause harm to unsuspecting users?

The constitution. Contact has limited working memory and with abort it it runs out.

hey @dan!!
i've posted my first blog .............plzzz check

Great read, hopefully great future is ahead of EOS.

Hey dan, I just have massive success but votes are so low, please have a look,thx

If only I knew how to code, reading this article would be much easier!

Nice post dan👍🏼 Eos is looking good

Do not forget my vote

Recibe el mio. Saludos

How would you regulate the contract between each user? Is there any legal foundation?

thanks great information

Nice
Incredible friend best regards from [email protected]

Way more interested in what happens to steem, you could build a lot of apllications around a working social network like that

A very useful post .., thank you @dan have given me the science, hopefully your post can be useful for stemian all over the world

Is it gonna have an app?...an interface to communicate like Steemit?...The name seems enticing each time i see it even though am just getting familiar with cryptos....

EOS is a platform to run Apps on, like Facebook runs Servers that run Facebooks backend (receiving and sending data)

What database do you think Facebook uses?

Facebook uses a series of database technologies :) It has one of the biggest MySQL installations, and also Hive, Cassandra, etc...

Awesome article Dan. Thanks for keeping us in the loop on the current updates and works of EOS. In light of recent price action this is most reassuring as us pro-EOS supporters are often told that it is an 'unproven' tech. Please keep us posted as you make further progress.

Hi, im here 3 weeks on steemit and i just want to say thank you for making this world interesting, better and developing into something new and revolutionary. Maybe you are not even aware of it and maybe it seems not a big deal to you but this a beginning of something amazing.

If it weren't for templates and operator overloading, C++ would be so sad lol.

Thanks for your update ,respect 🙂

Great post there about EOS.IO software @dan. It's shows that its really getting better and more advanced and seems to hold a great future in the currencies world

4 weeks on steemit and i'm amazed about the things you've created and what your vision for the future is.What a man really special,wish you the best of luck for your future projects.

I Am Getting Into EOS today ... So it sounds like EOS would make Stratis obsolete because EOS does the same but more (???) Yet, nobody really knows why things actually take off, but we are taught to be know it alls... I think that now is a great time to diversify your crypto holdings ... Getting Steem, ANS, Sia, IOTA, BAT, etc ... I just joined Steemit.com, I plan on posting a lot of Fun Art Content That I Make as well as Creative Crypto Content : Lets follow each other and enjoy this exciting ride (!!!) ~ mum

Many of theese altcoins have not proven themselves.

Thanks for the info. This helps people understand what EOS brings to the table

Thanks for sharing

Thank you dan!!

The app is very useful and always good information and share. I am very happy with your post I am from indonesia.best regard community steemit Indonesia

I don't even know C++. Just higher level languages so far (python, php, javascript). But I'm really glad this is out there. Thank you.

This is a great post, however this I don't comprehend @dan or someone please explain to me why this post is in negative?

He declines payout on a lot of his posts. Probably because he created Steem and doesn't think it would be fair to the rest of us Steemians if he took a lot of the rewards.

You are right @kendewitt just confirmed lol. He got a lot a dough Steempower and all. Must be the boss 4 sure.

Right. But he doesn't work on Steem anymore. He left it for the rest of the team to work on and is developing EOS now.

There are 3 pages
Pages