Open toonsevrin opened 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.
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));
Proposal: Convert to a name type within the database constructor
const db = new Database<Account>(N("accounts"));
to
const db = new Database<Account>("accounts");
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.
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);
}
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.
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);
@onlyOwner
should we allow guard methods as solidity with his own logic? or we start, for first version, w require_auth(whatever)
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.
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
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;
}
@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.
This possible in C++ only for types which provide "<<" overloaded operator as well as for AssemblyScript
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.
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++
And EOS eosiolib also using this operators for data streams: https://github.com/EOSIO/eos/blob/master/contracts/eosiolib/serialize.hpp#L32
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.
Of course, this if optional way
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.
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.