-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fungible Token standard #21
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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^8
or 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
alice
callswbtc::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^18
or as a number is1000000000000000000000
.High-level explanation
Alice needs to issue 2 transactions. The first one to
dai
to set an allowance forcompound
to be able to withdraw tokens fromalice
.The second transaction is to the
compound
to start the deposit process. Compound will check that the DAI tokens are supported and will try to withdraw the desired amount of DAI fromalice
.compound
can increase local ownership foralice
to 1000 DAIcompound
doesn't need to do anything in current example, but maybe can notifyalice
of unsuccessful transfer.Technical calls
alice
callsdai::set_allowance({"escrow_account_id": "compound", "allowance": "1000000000000000000000"})
.alice
callscompound::deposit({"token_contract": "dai", "amount": "1000000000000000000000"})
. During thedeposit
call,compound
does 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^8
or as a number is900100000000
.80 * 10^8
or as a number is8000000000
.1000000 * 10^8
or as a number is100000000000000
alex
towards 9001 wLTC.High-level explanation
Let's first setup open order by Alex on DEX. It's similar to
Token deposit to a contract
example 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
alex
callswbtc::set_allowance({"escrow_account_id": "dex", "allowance": "8000000000"})
.alex
callsdex::deposit({"token": "wbtc", "amount": "8000000000"})
.dex
callswbtc::transfer_from({"owner_id": "alex", "new_owner_id": "dex", "amount": "8000000000"})
alex
callsdex::trade({"have": "wbtc", "have_amount": "8000000000", "want": "wltc", "want_amount": "900100000000"})
.charlie
callswltc::set_allowance({"escrow_account_id": "dex", "allowance": "100000000000000"})
.charlie
callsdex::deposit({"token": "wltc", "amount": "100000000000000"})
.dex
callswltc::transfer_from({"owner_id": "charlie", "new_owner_id": "dex", "amount": "100000000000000"})
charlie
callsdex::trade({"have": "wltc", "have_amount": "900100000000", "want": "wbtc", "want_amount": "8000000000"})
.dex
callswbtc::transfer({"new_owner_id": "charlie", "amount": "8000000000"})
dex
callswltc::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_with
function 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