Conversation
|
Feedback by DanielRX: That's a good point. The standard doesn't need to define initialization function. Following the logic, ERC-20 shouldn't define names and symbols, cause it doesn't affect other contracts or how they interact with ERC-20. Locks are important for the async functionality. Without locks, it's not possible to have an escrow DEX from the 3rd example. The 3rd examples is a building block on how to interact with 2 token contracts at the same time. It's possible to avoid lock/unlock and use temporary transfers to an escrow account itself, but it affects the allowance in case of rollback. For example:
Now the DEX allowance of the reverted original owner has decreased. So the DEX can't try to transfer again. And the owner has to reset the allowance back. |
Names and symbols do affect other contracts and how they interact, they are public interfaces. If the name was internal, it wouldn't need to define them as having any meaning, but they are defined so that consumers (markets, web UIs, other contracts) can know that if it is ERC20, the name, symbol, total supply, etc mean a certain thing.
I'm not saying "Locks shouldn't be standardized", but should all tokens, regardless of their usage, have to implement locking? The base standard (as I said), should be the intersection of all Fungible Token use cases Having "fungible tokens" is having tokens that can be transferred. Arguably you could say approvals fall into this, but locking is not part of a fungible token, it's part of a The key point is that |
|
Since my comments about not putting the entire discussion on GH were ignored, I will put them here myself.Why should the base token interface contain locking? The same can be said for minting (and why ERC20 is not a mintable token by default) Why can't the base token avoid the locking, and build a second token standard on top of that? Is locking needed for fungible tokens, or merely for "Market Integrated Tokens", "Escrow Tokens" or "Lockable tokens"? |
|
@DanielRX I hear your feedback about splitting lockable from the base transferable token. Will update this NEP to split the interface into 3 parts: basic (includes only transfer), escrow (enables allowance and transferFrom, and lockable (enables locks)). The reason I put entire conversation on github is to keep the full discussion for other reviewers. Please let me know through DM if you don't want to put your comments on github. I'll update it. |
|
The progress on what is blocking this can be viewed: #26 |
|
I've updated the standard (removed locks), added the spec. Needs review |
behaviary
left a comment
There was a problem hiding this comment.
Needs SAFEs. Just adding this to kill notification until we are implementing that.
@potatodepaulo |
|
Your Render PR Server URL is https://nomicon-pr-21.onrender.com. Follow its progress at https://dashboard.render.com/static/srv-bqsq7m1j38far37cn5j0. |
|
Your Render PR Server at https://nomicon-pr-21.onrender.com is now live! View it on your dashboard at https://dashboard.render.com/static/srv-bqsq7m1j38far37cn5j0. |
behaviary
left a comment
There was a problem hiding this comment.
Concerns:
- Escrow is technically required, but we don't have a fleshed out standards. Would you advocate for the simplest use case to just implement their own escrow?
- We address that get_balance is unreliable since state can change between queries. I think allowances are acceptable. But this points to problem one, which is that there is an implied standard for escrows.
|
@potatodepaulo, there are 2 escrow examples:
Escrow acts just like a regular account that holds tokens. It maintains internal mapping between token holders and their corresponding balances. The spec for escrow is not required for the standard. |
|
Update: Talked with @evgenykuzyakov. We will abstract much of the use case specific parts of fungible token standard to other standard (for instance Escrow, and MetaData). |
Fungible Token
Summary
A standard interface for fungible tokens allowing for ownership, escrow and transfer, specifically targeting third-party marketplace integration.
Motivation
NEAR Protocol uses an asynchronous sharded Runtime. This means the following:
While this increases the transaction throughput linearly with the number of shards, it also creates some challenges for cross-contract development.
For example, if one contract wants to query some information from the state of another contract (e.g. current balance), by the time the first contract receive the balance the real balance can change.
It means in the async system, a contract can't rely on the state of other contract and assume it's not going to change.
Instead the contract can rely on temporary partial lock of the state with a callback to act or unlock, but it requires careful engineering to avoid dead locks.
In this standard we're trying to avoid enforcing locks, since most actions can still be completed without locks by transferring ownership to an escrow account.
Prior art:
Guide-level explanation
We should be able to do the following:
There are a few concepts in the scenarios above:
Note, that the precision is not part of the default standard, since it's not required to perform actions. The minimum
value is always 1 token.
Simple transfer
Alice wants to send 5 wBTC tokens to Bob.
Assumptions
wbtc.alice.bob.10^8.5 * 10^8or as a number is500000000.High-level explanation
Alice needs to issue one transaction to wBTC contract to transfer 5 tokens (multiplied by precision) to Bob.
Technical calls
alicecallswbtc::transfer({"new_owner_id": "bob", "amount": "500000000"}).Token deposit to a contract
Alice wants to deposit 1000 DAI tokens to a compound interest contract to earn extra tokens.
Assumptions
dai.alice.compound.10^18.1000 * 10^18or as a number is1000000000000000000000.High-level explanation
Alice needs to issue 2 transactions. The first one to
daito set an allowance forcompoundto be able to withdraw tokens fromalice.The second transaction is to the
compoundto start the deposit process. Compound will check that the DAI tokens are supported and will try to withdraw the desired amount of DAI fromalice.compoundcan increase local ownership foraliceto 1000 DAIcompounddoesn't need to do anything in current example, but maybe can notifyaliceof unsuccessful transfer.Technical calls
alicecallsdai::set_allowance({"escrow_account_id": "compound", "allowance": "1000000000000000000000"}).alicecallscompound::deposit({"token_contract": "dai", "amount": "1000000000000000000000"}). During thedepositcall,compounddoes the following:dai::transfer_from({"owner_id": "alice", "new_owner_id": "compound", "amount": "1000000000000000000000"}).compound::on_transfer({"owner_id": "alice", "token_contract": "dai", "amount": "1000000000000000000000"}).Multi-token swap on DEX
Charlie wants to exchange his wLTC to wBTC on decentralized exchange contract. Alex wants to buy wLTC and has 80 wBTC.
Assumptions
wltc.wbtc.dex.charlie.alex.10^8.9001 * 10^8or as a number is900100000000.80 * 10^8or as a number is8000000000.1000000 * 10^8or as a number is100000000000000alextowards 9001 wLTC.High-level explanation
Let's first setup open order by Alex on DEX. It's similar to
Token deposit to a contractexample above.Then Charlie comes and decides to fulfill the order by selling his wLTC to Alex on DEX.
Charlie calls the DEX
When called, DEX makes 2 async transfers calls to exchange corresponding tokens.
Technical calls
alexcallswbtc::set_allowance({"escrow_account_id": "dex", "allowance": "8000000000"}).alexcallsdex::deposit({"token": "wbtc", "amount": "8000000000"}).dexcallswbtc::transfer_from({"owner_id": "alex", "new_owner_id": "dex", "amount": "8000000000"})alexcallsdex::trade({"have": "wbtc", "have_amount": "8000000000", "want": "wltc", "want_amount": "900100000000"}).charliecallswltc::set_allowance({"escrow_account_id": "dex", "allowance": "100000000000000"}).charliecallsdex::deposit({"token": "wltc", "amount": "100000000000000"}).dexcallswltc::transfer_from({"owner_id": "charlie", "new_owner_id": "dex", "amount": "100000000000000"})charliecallsdex::trade({"have": "wltc", "have_amount": "900100000000", "want": "wbtc", "want_amount": "8000000000"}).dexcallswbtc::transfer({"new_owner_id": "charlie", "amount": "8000000000"})dexcallswltc::transfer({"new_owner_id": "alex", "amount": "900100000000"})Reference-level explanation
The full implementation in Rust can be found there: https://github.com/nearprotocol/near-sdk-rs/blob/master/examples/fungible-token/src/lib.rs
NOTES:
2**128 - 1)."100". This is done to avoidJSON limitation of max integer value of
2**53.Interface:
Drawbacks
It should be possible if we introduce
transfer_withfunction that transfers tokens and calls escrow contract. It needs to handle result of the execution and contracts have to be aware of this API.Future possibilities