-
Notifications
You must be signed in to change notification settings - Fork 161
Proposal: Allowance-free vault-based token standard #122
Copy link
Copy link
Closed
Description
Rational:
- remove the extra space that is used for allowance
- remove an extra transaction that is required to transfer token to a contract (to set allowance)
- simplify token standard usage
- make chained transactions possible (even, though they are complicated)
- ideally remove
#[payable]requirements, because it's not easily abusable. You'd have to transfer to non existing accounts. This can be addressed with minimum token balance.
Background
There are a few reasons for allowance:
- Allow to withdraw tokens from the owner's account by the contract at any time.
This can be solved by depositing tokens to the contract first and it can spend the tokens on your behalf. - Legacy design model where in order to act on your behalf, you first authorize the contract and then it can
withdraw from you when you initialized the transfer. This can be addressed through a callback.
Allowance is also often abused by dApps setting unlimited allowance all the time, so it defeats the purpose.
Safe-based transfers
Instead of having permanent allowance, we can introduce a one-time temporary allowance that only lives for the
duration of the transaction. We call this a safe. This idea is very similar to Auto-unlock with Safes idea, but it doesn't require protocol changes in the nearcore Runtime.
It works the following way:
- An owner calls
transfer_with_safewhere the receiving side is a contract. - The token contract withdraws the
amountof tokens from the owner and temporary locks them. - Then it calls a receiving contract with a unique identifier that can be used to access locked tokens.
- The receiving contract can withdraw up to the
amountof token from the temporary lock and use them. - Once the receiving contract call is done, the callback on the token contract is triggered and it returns the unspent tokens from the lock.
We call this temporary lock a safe.
Implementation
Token interface
/// Simple transfers
/// Gas requirement: 5 TGas or 5000000000000 Gas
/// Should be called by the balance owner.
///
/// Actions:
/// - Transfers `amount` of tokens from `predecessor_id` to `receiver_id`.
pub fn transfer_unsafe(&mut self, receiver_id: ValidAccountId, amount: U128);
/// Transfer to a contract with payload
/// Gas requirement: 40+ TGas or 40000000000000 Gas.
/// Consumes: 30 TGas and the remaining gas is passed to the `receiver_id` (at least 10 TGas)
/// Should be called by the balance owner.
/// Returns a promise, that will result in the unspent balance from the transfer `amount`.
///
/// Actions:
/// - Withdraws `amount` from the `predecessor_id` account.
/// - Creates a new local safe with a new unique `safe_id` with the following content:
/// `{sender_id: predecessor_id, amount: amount, receiver_id: receiver_id}`
/// - Saves this safe to the storage.
/// - Calls on `receiver_id` method `on_token_receive(sender_id: predecessor_id, amount, safe_id, payload)`/
/// - Attaches a self callback to this promise `resolve_safe(safe_id, sender_id)`
pub fn transfer_with_safe(
&mut self,
receiver_id: ValidAccountId,
amount: U128,
payload: String,
) -> Promise;
/// Withdraws from a given safe
/// Gas requirement: 5 TGas or 5000000000000 Gas
/// Should be called by the contract that owns a given safe.
///
/// Actions:
/// - checks that the safe with `safe_id` exists and `predecessor_id == safe.receiver_id`
/// - withdraws `amount` from the safe or panics if `safe.amount < amount`
/// - deposits `amount` on the `receiver_id`
pub fn withdraw_from_safe(
&mut self,
safe_id: SafeId,
receiver_id: ValidAccountId,
amount: U128,
);
/// Resolves a given safe
/// Gas requirement: 5 TGas or 5000000000000 Gas
/// A callback. Should be called by this fungible token contract (`current_account_id`)
/// Returns the remaining balance.
///
/// Actions:
/// - Reads safe with `safe_id`
/// - Deposits remaining `safe.amount` to `sender_id`
/// - Deletes the safe
/// - Returns the total withdrawn amount from the safe `original_amount - safe.amount`.
/// #[private]
pub fn resolve_safe(&mut self, safe_id: SafeId, sender_id: AccountId) -> U128;Receiving side interface
/// Called when a given amount of tokens is locked in a safe by a given sender with payload.
/// Gas requirements: 2+ BASE
/// Should be called by the fungible token contract
///
/// This methods should withdraw tokens from the safe and act on them. When this method returns a value, the
/// safe will be released and the unused tokens from the safe will be returned to the sender.
/// There are bunch of options what the contract can do. E.g.
/// - Option 1: withdraw and account internally
/// - Increase inner balance by `amount` for the `sender_id` of a token contract ID `predecessor_id`.
/// - Promise call `withdraw_from_safe(safe_id, receiver_id: env::current_account_id(), amount)` to withdraw the amount to this contract
/// - Return the promise
/// - Option 2: Simple redirect to another account
/// - Promise call `withdraw_from_safe(safe_id, receiver_id: ANOTHER_ACCOUNT_ID, amount)` to withdraw to `ANOTHER_ACCOUNT_ID`
/// - Return the promise
/// - Option 3: Partial redirect to another account (e.g. with commission)
/// - Promise call `withdraw_from_safe(safe_id, receiver_id: ANOTHER_ACCOUNT_ID, amount: ANOTHER_AMOUNT)` to withdraw to `ANOTHER_ACCOUNT_ID`
/// - Chain with (using .then) promise call `withdraw_from_safe(safe_id, receiver_id: env::current_account_id(), amount: amount - ANOTHER_AMOUNT)` to withdraw to self
/// - Return the 2nd promise
/// - Option 4: redirect some of the payments and call another contract `NEW_RECEIVER_ID`
/// - Promise call `withdraw_from_safe(safe_id, receiver_id: current_account_id, amount)` to withdraw the amount to this contract
/// - Chain with promise call `transfer_with_safe(receiver_id: NEW_RECEIVER_ID, amount: SOME_AMOUNT, payload: NEW_PAYLOAD)`
/// - Chain with the promise call to this contract to handle callback (in case we want to refund).
/// - Return the callback promise.
on_receive_with_safe(sender_id: ValidAccountId, amount: U128, safe_id: SafeId, payload: String);Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels