This week we made great strides toward refining the architecture of EOS and defining the developer API. In particular we have identified a programming model that should enable parallelism while maximizing ease of use and clarity of the code.
Updated Example Contracts
Those who have been following github may have noticed that we have started to integrate a number of example contracts to test our architecture and ensure we can cover the desired use cases.
This is what a simple currency contract looks like today:
/**
* 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;
uint64_t amount = 0;
char memo[]; /// extra bytes are treated as a memo and ignored by logic
};
struct CurrencyAccount {
CurrencyAccount( uint64_t b = 0 ):balance(b){}
uint64_t balance = 0;
/** used as a hint to Db::store to tell it which table name within the current
* scope to use. Data is stored in tables under a structure like
*
* scope/code/table/key->value
*
* In this case the "singleton" table is designed for constant named keys that
* refer to a unique object type. User account balances are stored here:
*
* username/currency/singleton/account -> CurrencyAccount
*/
static Name tableId() { return NAME("singleton"); }
};
void apply_currency_transfer() {
const auto& transfer = currentMessage<Transfer>();
/** will call apply_currency_transfer() method in code defined by transfer.to and transfer.from */
requireNotice( transfer.to, transfer.from );
requireAuth( transfer.from );
static CurrencyAccount from_account;
static CurrencyAccount to_account;
Db::get( transfer.from, NAME("account"), from_account );
Db::get( transfer.to, NAME("account"), to_account );
assert( from_account.balance >= transfer.amount, "insufficient funds" );
from_account.balance -= transfer.amount;
to_account.balance += transfer.amount;
if( from_account.balance == 0 )
Db::remove<CurrencyAccount>( transfer.from, NAME("account") );
else
Db::store( transfer.from, NAME("account"), from_account );
Db::store( transfer.to, NAME("account"), to_account );
}
There are several notable aspects about this currency contract:
- it looks like normal sequential code
- but it can transfer among two pairs of users in parallel
What does this mean? It means that while Alice is transferring to Bob, Sam can be transferring to Jill. The performance of this currency contract is no longer limited by the single threaded performance constraints of prior currency contracts.
Start of Exchange Contract
We have also implemented a simple exchange contract that accepts deposits and withdraws from two different currency contracts:
struct Account {
uint64_t a = 0;
uint64_t b = 0;
int open_orders = 0;
bool isEmpty()const { return !(a|b|open_orders); }
/**
* Balance records for all exchange users are stored here
* exchange/exchange/balance/username -> Balance
*/
static Name tableId() { return Name("balance"); }
};
/**
* This method is called after the "transfer" action of code
* "currencya" is called and "exchange" is listed in the notifiers.
*/
void apply_currencya_transfer() {
const auto& transfer = currentMessage<Transfer>();
if( transfer.to == "exchange" ) {
static Balance to_balance;
Db::get( transfer.from, to_balance );
to_balance.a += transfer.amount;
Db::store( transfer.from, to_balance );
} else if( transfer.from == "exchange" ) {
requireAuth( transfer.to ); /// require the reciever of funds (account owner) to authorize this transfer
static Balance to_balance;
auto balance = Db::get( transfer.to, to_balance );
assert( balance.a >= transfer.amount, "insufficient funds to withdraw" );
balance.a -= transfer.amount;
if( balance.isEmpty() )
Db::remove<Balance>( transfer.to );
else
Db::store( transfer.to, to_balance );
} else {
assert( false, "notified on transfer that is not relevant to this exchange" );
}
}
/**
* This method is called after the "transfer" action of code
* "currencya" is called and "exchange" is listed in the notifiers.
*/
void apply_currencyb_transfer() {
const auto& transfer = currentMessage<Transfer>();
if( transfer.to == "exchange" ) {
static Balance to_balance;
Db::get( transfer.from, to_balance );
to_balance.b += transfer.amount;
Db::store( transfer.from, to_balance );
} else if( transfer.from == "exchange" ) {
requireAuth( transfer.to ); /// require the reciever of funds (account owner) to authorize this transfer
static Balance to_balance;
auto balance = Db::get( transfer.to, to_balance );
assert( balance.b >= transfer.amount, "insufficient funds to withdraw" );
balance.b -= transfer.amount;
if( balance.isEmpty() )
Db::remove<Balance>( transfer.to );
else
Db::store( transfer.to, to_balance );
} else {
assert( false, "notified on transfer that is not relevant to this exchange" );
}
}
What is interesting about this implementation is that deposits and withdraws occur without any asynchronous callbacks or other complex pending states. Rather than the user asking the exchange to tell the currency contract to withdraw funds, the exchange gives everyone permission to transfer from the exchange with the caveat that the exchange's handler for transfers is called on both deposits and withdraws. If a user attempts to withdraw more than their balance with the exchange contract the handler will reject the transfer attempt.
We have not quite yet implemented working tests of the exchange contract deposit and withdraw, but that is on the agenda for next week. We will also be implementing a basic social media application to prove that we have all the existing use cases covered.
Synchronous Calls
One of the biggest developments this week as an architecture that allows synchronous calls among tightly coupled applications while still retaining most of the benefits of parallelism. A future blog post will go into greater detail on our memory and parallel execution structure.
Cost of Storage
Some people have expressed concerns that if tokens are worth $30 billion dollars and there is only 1 TB of storage capacity that the cost of storage will be too high. I have prepared an article that explains how we address this and keep the cost of storage mostly independent from token price.
EOS.IO development is making great strides!