eos-typescript / examples

These contracts are nonfunctional for now, they serve as a model for the project
MIT License
10 stars 4 forks source link

Ideal smart contract #1

Open toonsevrin opened 6 years ago

toonsevrin commented 6 years ago

I wrote a very small proposal contract here. The goal is to propose and discuss improvements on the way the library works until we reach a really smooth user experience. After that the library can be written.

toonsevrin commented 6 years ago

Proposal: Use interfaces for the structures

from

let acc = new Account(to, quantity);

class Account {
    @primary
    owner: name;
    balance: u64;

    constructor(owner: name, balance: u64) {
        this.owner = owner;
        this.balance = balance;
    }
}

to

let acc = {"owner": to, "balance": quantity};

interface Account {
    @primary
    owner: name;
    balance: uint64;
}

or perhaps simply:

let acc = new Account(to, quantity);
class Account {
    constructor(@primary public owner: name, public balance: u64) {}
}

Information about TypeScript interfaces. TypeScript would not allow for the @primary decorator, however.

toonsevrin commented 6 years ago

Proposal: MongoDB like database library

function addBalance(to: name, quantity: u64): void{
    db.update(to, Database.increment("balance", quantity), UpdateOptions.upsert(true));
}

or with operator overloading (probably not valid assemblyscript):

db.update(to, F("balance") += quantity), UpdateOptions.upsert(true));

F would return some sort of field class instance here.

or with fat arrow (upsert mechanism?):

db.update(to, (acc: Account): void => { 
     acc.balance += quantity;
}, UpdateOptions.upsert(true));
toonsevrin commented 6 years ago

Proposal: Convert to a name type within the database constructor

const db = new Database<Account>(N("accounts"));

to

const db = new Database<Account>("accounts");
toonsevrin commented 6 years ago

Issue: The @database decorator is not valid typescript. You can only decorate methods, classes and method parameters. We need some way to inform our transpiler about the database though.

toonsevrin commented 6 years ago

Proposal authentication decorator

Not sure if this is an improvement but allow for:

@action
function issue(@authenticated to: name, quantity: u64): void{
    addBalance(to, quantity);   
}
toonsevrin commented 6 years ago

proposal MongoDB like conditional updates

from:

let fromAccount: Account = db.get(from);
eosio_assert(fromAccount.balance >= quantity, "overdrawn balance");
fromAccount.balance -= quantity;
db.update(fromAccount);

to:

let success = db.update(from, Database.gt("balance", quantity), Database.decr("balance", quantity));
eosio_assert(success, "overdrawn balance");

or with overloading (this is again probably not valid typescript):

 db.update(from, F("balance") > balance, F("balance") -= quantity, "overdrawn balance");

F(string) encapsulates the string to some sort of Field object.

or with fat arrows (typing required here?):

 db.update(from, 
    (acc: Account): bool => acc.balance > balance , 
    (acc: Account): void => { acc.balance += quantity; }, 
    "overdrawn balance");

This is probably not an improvement, as readability is reduced and complexity is increased. The transaction is atomic anyways.

NOTE: Actual MongoDB compatibility would not be ideal, as it has no cross document acid guarantee, while EOS does offer this.

Being compatible with an ACID database makes thus more sense, perhaps compatibility with sql dbs with an object oriented model would makes more sense. Here is an example of how that could look.

MaxGraey commented 6 years ago

Instead this:

const owner = N("owneraccount");
@action
function issue(to: name, quantity: u64): void{
  require_auth(owner);
  addBalance(to, quantity);   
}

Propose:

@action 
@only("owneraccount")
function issue(to: name, quantity: u64): void {
  addBalance(to, quantity);   
}

or simpler and specially for owner (Solidity style):

@action 
@onlyOwner 
function issue(to: name, quantity: u64): void {
  addBalance(to, quantity);   
}

After desugaring:

const owner = N("owneraccount");
const issue = (to: name, quantity: u64): void => {
  require_auth(owner);
  addBalance(to, quantity);   
};
add_action(N("issue"), issue);
kesar commented 6 years ago

@onlyOwner

should we allow guard methods as solidity with his own logic? or we start, for first version, w require_auth(whatever)

toonsevrin commented 6 years ago

Not sure if eos allows us to fetch the contract owner programatically, I'll be pressuring to implement that. Actually I think the receiver in the apply function may just simply be the contract owner's name.

Secondly: The desugared code would probably not work with AssemblyScript (due to runtime reflection required in the add_action backend), here's my proposal for the desugared typescript:

const owner = N("owneraccount");
function issue(to: name, quantity: u64): void {
  require_auth(owner);
  addBalance(to, quantity);   
}
export function apply(receiver: u64, code: u64, action: u64){
    ds: DataStream = getActionDataDS();
    if(action == N("issue")){
        issue(ds.read<u64>(), ds.read<u64>());
    }
}

Of course the apply function would have some more content.

MaxGraey commented 6 years ago

AssemblyScript now support full set of operator overloading. So just imagine how looks like contracts will be:

@action
@onlyOwner
export function transfer(from: AccountName, to: AccountName, quantity: u64): void {
  Account.get(to) << quantity << Account.get(from);
}

More details of realization with some limitation (which resolve in future) you can find here: https://webassembly.studio/?f=tz9pgrvb7u

MaxGraey commented 6 years ago

C++ developers pretty good familiar with this syntax which using in different streams:

#include <iostream> 
int main() {
  int val = 65;

  std::cout << std::right;       // right-adjusted (manipulator)
  std::cout << std::setw(10);    // set width (extended manipulator)

  std::cout << val << std::endl;
}
toonsevrin commented 6 years ago

@MaxGraey cool!

int bal1 = 10;
int bal2 = 0;
bal2 << 10 << bal1; //result: {"bal1": 0, "bal2": 10}

If that was possible in cpp, this would be a no-brain addition to the library, but I believe that doesn't work.

MaxGraey commented 6 years ago

This possible in C++ only for types which provide "<<" overloaded operator as well as for AssemblyScript

toonsevrin commented 6 years ago

Yes, all though it's nice that AS allows for operator overloading, << is usually used for a bitwise shift. Using it for something else is a bit unnatural, no?

My opinion is that it would be better to learn our devs a general toolkit to do all kinds of database operations, instead of using a special syntax for a special case (transfer). This is why I would prefer a Mongo like library and/or simply fat arrow updates.

MaxGraey commented 6 years ago

As I see transfer operation is one of major operations in any contracts and for EOS not so simple (read from database account data, validate, increase or decrease amount, store back to db updated amount and etc). So to prevent of mistakes during this operation makes sense provide more high level interface. In my opinion using stream operator pretty clear and similar conversions used by C++

MaxGraey commented 6 years ago

And EOS eosiolib also using this operators for data streams: https://github.com/EOSIO/eos/blob/master/contracts/eosiolib/serialize.hpp#L32

kesar commented 6 years ago

Mixed feelings here. I also somehow feel its kind of unnatural, probably based in my background dev languages. It's true that overload operators its pretty common patter in C++ and other languages.

If we are trying to build a "typescript" library, what's more common in TS / JS world?

Perhaps we should support both flavours.

MaxGraey commented 6 years ago

Of course, this if optional way

toonsevrin commented 6 years ago

Interesting, I agree that having a "safe" transfer function makes a lot of sense, to prevent mistakes. We'll include that, perhaps through a different means than the bitwise operator.