Skip to content
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

Add SIP: Composable Fungible Tokens with Allowance #154

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
152 changes: 152 additions & 0 deletions sip-0xx-improve-sip10/sip-0xx-composable-fungible-token-standard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# SIP-0XX: Composable Fungible Tokens with Allowance

## Preamble

SIP Number: 0XX
Title: Composable Fungible Tokens with Allowance
Author: Jose Orlicki <[email protected]>
Consideration: Technical
Type: Standard
Status: Draft
Created: [Date]
License: CC0-1.0
Sign-off: [Sign-off Name] <[email protected]>

## Abstract

This proposal extends the SIP-010 standard trait for fungible tokens on the Stacks blockchain to support composable fungible tokens with allowances. It addresses the limitations of the previous standard, which did not provide sufficient support for composability and security. SIP-10 is the bare minimum to have a standard for a fungible token, featuring mainly token tranfers but the payments is limited or unsafe in the current form. To have payments and deposits to third-party services, in a way that is atomic and composable your need allowances. The new trait includes functions for transferring tokens (`transfer` limited to the `contract-caller` sender), approving allowances (`approve` and `revoke`), checking allowances (`allowance`), and transferring tokens leveraging allowances (`transfer-from`). The recommended implementation of `approve` uses incremental allowances to avoid race conditions and double transfering.

jio-gl marked this conversation as resolved.
Show resolved Hide resolved
## Motivation

The previous fungible token standard (SIP-010) had limitations that hindered composability in decentralized finance (DeFi) contracts. Specifically, it lacked a mechanism for users to grant allowances to other users or contracts, similar to signing a check or how POS Debit Card systems work. Additionally, the previous standard's resulted in applications including de-facto checks based on `tx-sender` that could lead to security vulnerabilities.

This proposal aims to enhance the fungible token standard to enable safer and more flexible composability in DeFi and other applications on the Stacks blockchain.

## Specification

### Extended Trait Functions

This proposal extends the SIP-010 trait with the following functions:

#### transfer

`(transfer (from principal) (to principal) (amount uint) (response bool uint))`
jio-gl marked this conversation as resolved.
Show resolved Hide resolved

Transfer the specified amount of tokens from one principal to another. The `from` principal must always match the contract caller `contract-caller`, ensuring that only authorized parties can initiate transfers. Do not check and allow the execution if `sender` is `tx-sender`, this results in security weaknesses that make phishing and arbitrary token execution very dangerous (read https://www.coinfabrik.com/blog/tx-sender-in-clarity-smart-contracts/).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have post-conditions. Therefore, not very dangerous IMO, but it is useful to allow implementation of send-many contracts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This security model (no transfers for tx-sender) limits composability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no transfer for tx-sender limits the 1-hop DeFi that current works de-facto. If I reason right for 2-hops or 3-hops DeFi then the tx-sender approach does not work (see new Rationale).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tx-sender does not limit transfers to only 1 hop. You can do as many hops as you want (within the limits of execution costs of course).
In fact it provides greater composability and flexibility than contract-caller


#### transfer-from

`(transfer-from (from principal) (to principal) (amount uint) (response bool uint))`

Transfer a specified amount of tokens from one principal to another using an allowance. The `from` principal must have previously approved the allowance for the `to` principal to transfer tokens on their behalf. This function facilitates composability by allowing third-party transfers within the approved limits.

#### approve
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe allow-contract-caller like in pox contract

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems too long, what about allow ?


`(approve (spender principal) (amount uint) (response bool uint))`

Approve an incremental allowance for a specific principal or contract to spend a certain amount of tokens on behalf of the sender. This function is similar to signing a check, granting permission for a third party to make token transfers within the specified limit. This allowance must be incremental (it adds on top of previous allowances) to avoid race condition situations where an `transfer-from` call is executed before the `approve` and then another after the `approve` call.

#### revoke
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe disallow-contract-caller like in pox contract

Copy link
Author

@jio-gl jio-gl Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems a bit long, maybe disallow ??


`(revoke (spender principal) (response bool uint))`

Revoke an existing allowance granted to a specific principal or contract. This function sets the allowance for the specified spender to 0, effectively removing their permission to spend tokens on behalf of the sender. It provides a mechanism for the sender to revoke previously granted permissions when they are no longer needed or desired. You usually give a limited or exact allowance to a DeFi service, this service will grab the amount of token you approved and then provide some financial service. Is common practice in Web3 to give infinite or large allowance to Dapps to do only one `approve` call per token. But if you have given an infinite or large allowance to the Defi contract (a convenient common practice) and after the DeFi services have grabbed your tokens, you can revoke the infinite allowance by calling `revoke`. This helps mitigate the impact of bugs found in the DeFi contract in the future where an attacker might try to grab more of your tokens. If you intend to use the DeFi services only once or you no longer trust the service, you should call `revoke` immediately.

#### allowance

`(allowance (owner principal) (spender principal) (response uint uint))`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better get-allowance or even get-allowance-contract-caller to be similar to get-balance using in SIP-10 and get-allowance-contract-caller used in pox contracts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok sounds good. Do we want the standards to always return a response or it can be a pure uint value?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trait can't have functions that returns anything but response.


Check the remaining allowance of tokens that the `spender` principal is authorized to transfer on behalf of the `owner` principal. This function is useful for applications that need to verify the available allowance before initiating token transfers.

### Other Trait Functions

The new trait should also include the functions defined in SIP-010, including `get-name`, `get-symbol`, `get-decimals`, `get-balance`, `get-total-supply`, and `get-token-uri`.

## Trait Implementation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would defined only the newly added functions in the trait. Contracts can implement both the sip-10 trait and this trait.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though about doing that, but I felt it was better to replace the whole trait altogether to avoid the hazzle of implementing 2 traits and avoid the confusion with tx-sender that is one of the main points of the SIP. I was afraid that people implement both traits but continue checking tx-sender and avoding the new functions. From my point of view is easy to do a simpler SIP without the other functions but I was afraid that it will not replace the prev standard.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would defined only the newly added functions in the trait. Contracts can implement both the sip-10 trait and this trait.

Currently, I think I might limit this Improve Proposal to newly added functions, then the market can decide if they want to use allowances or limit to current approach. We can publish Best Practices for SIP10 that should be a combination of: tx-sender OR contract-caller check, post-conditions. If the main web3 apps do not embrace allowances no one will probably do it.


The extended trait `sip-0xx-trait` that includes the functions from `sip-010-trait` and the new functions introduced in SIP-0XX:

```clarity
(define-trait sip-0xx-trait
(
;; Transfer from the caller to a new principal
;; first principal, sender, must be always equal to contract-caller
(transfer (uint principal principal (optional (buff 34))) (response bool uint))

;; the human readable name of the token
(get-name () (response (string-ascii 32) uint))

;; the ticker symbol, or empty if none
(get-symbol () (response (string-ascii 32) uint))

;; the number of decimals used, e.g. 6 would mean 1_000_000 represents 1 token
(get-decimals () (response uint uint))

;; the balance of the passed principal
(get-balance (principal) (response uint uint))

;; the current total supply (which does not need to be a constant)
(get-total-supply () (response uint uint))

;; an optional URI that represents metadata of this token
(get-token-uri () (response (optional (string-utf8 256)) uint))

;; Transfer from one principal to another using an allowance
(transfer-from (uint principal principal uint) (response bool uint))

;; Approve an incremental allowance for a specific principal to spend tokens
(approve (principal uint) (response bool uint))

;; Revoke an allowance, goes to 0, for a specific principal to spend tokens
(revoke (principal) (response bool uint))

;; Check the remaining allowance of tokens for a spender
(allowance (principal principal) (response uint uint))
)
)
```

This extended trait, `sip-0xx-trait`, includes the functions from the original `sip-010-trait` and adds the new functions introduced in SIP-0XX: `transfer-from`, `approve`, `revoke`, and `allowance`. Developers can use this trait as a reference when implementing composable fungible tokens with allowances on the Stacks blockchain.

## Rationale

The extension of the SIP-010 trait with allowances and the ability to transfer tokens using allowances addresses the limitations of the previous standard. By introducing allowances, users can grant explicit permission for third parties to spend tokens on their behalf, improving the security and composability of DeFi contracts. The inclusion of additional functions from SIP-010 ensures compatibility with existing standards.

### Limiting Phishing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is Phishing? Could you elaborate?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will include more information in the doc.
These days is mainly a Fake Airdrop, read but you probably have alread read it https://www.coinfabrik.com/blog/tx-sender-in-clarity-smart-contracts/


With this new approach that has only a check for `(is-eq sender contract-caller)`, and in case of successful phishing attempt, the malicious Dapp has to ask for allowance for the specific token and drain that token, so the they have to request 2 transactions and can only drain 1 token. The previous standard, and the de-facto check of `(is-eq sender tx-sender)`, and the phishing attempt is successful, with a single transaction they can drain all of our standard tokens (several contracts) that only check the `tx-sender`.

### DeFi Composability Pattern

The most common DeFi pattern, that is supported by this new standard is:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this can't be done with SIP-10. In 5. the user could also transfer the amount of tokens required for example-defi-service. Are there blocks between 5. and 6.?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be done with SIP10 with transfer only if tokens check tx-sender, if lets say DeFiA uses DeFiB and DeFiA transfer your tokens from your wallet to DeFiB. Then you give total control to the DeFiA and also to DeFiB called by DeFiA. Not sure your tokens can be retrieved properly because your tokens are jumping from your wallet to DeFiB.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, why do we need allowances? Is it only about the issue of tx with allow mode?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you have DeFiA is a YieldAggregator and DeFiB is a YieldService, then the following 3 current approaches does not work I think:

a) User calls defi-service from DeFiA.
b) DeFiA does transfer(User,DeFiA), good tx-sender.
c) In the same transaction DeFiA cannot do transfer(DeFiA,DefiB) because the tx-sender is still User.

a) User calls defi-service from DeFiA.
b) DeFiA does transfer(User,DeFiB), good tx-sender directly from User.
c) Incorrect, DeFiB does not supports receiving random token from third parties without calling another-defi-service from DeFiB.

a) User calls defi-service from DeFiA.
b) DeFiA does transfer(User,DeFiA), good tx-sender.
c) DeFiA calls another-defi-service from DeFiB.
d) Incorrect, tx-sender is still User, if DeFiB calls transfer(DeFiA,DeFiB) it has no permission.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1. c) DeFiA can use as-contract to switch context, but needs to be careful. Furthermore, the token can allow transfers where sender is contract-caller OR tx-sender. Then no context switch is necessary.

In 3. c) Similarily, DeFiA can use as-contract to switch context, but needs to be careful.

In 3. DeFiA could not do b) (transfer tokens from User to DeFiA), but let DeFiB do it in d).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okey, so the solution for you is to change tx-sender to local contract by using as-contract.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


1. The Dapp (decentralized application) _D_ generates a `approve` transaction for a single standard token _T_ and a single Dapp contract _C_.
2. The User signs the `approve` transaction and submits to blockchain.
3. The token _T_ get the allowance updated when the transaction ends on-chain.
4. The Dapp (decentralized application) _D_ generates a service `example-defi-service` transaction to start the service.
5. The User signs the `example-defi-service` and submits to blockchain.
6. The Dapp _D_ executes `example-defi-service` on-chain, this includes calling `transfer-from` to retrieve the tokens from User and, eventually, forwarding the tokens to a third-party service with `approve`, thus allowing for _Composability_.

## Backwards Compatibility
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are SIP-10 token contracts on mainnet like vibe tokens

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I see is that without allowances you are giving total control to any DeFi service (that only checks tx-sender) to grab any amount of any of your SIP10 Tokens?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, there are 40 contracts with approve function, but sometimes they use it for access control or the signature is different, they also include the owner in the signature.

https://github.com/search?q=repo%3Aboomcrypto%2Fclarity-deployed-contracts+define-public+%22%28approve+%22&type=code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For that we have post conditions @jo-tm

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only 5 tokens seems to have allowance or allowance-of I think the rest uses approve for Access Control Roles.

https://github.com/search?q=repo%3Aboomcrypto%2Fclarity-deployed-contracts+define-public+%22%28allowance-of+%22&type=code


This proposal aims to maintain compatibility with the existing SIP-010 standard while introducing new functionality. Existing fungible token contracts can continue to use the SIP-010 functions without modification. Contracts that wish to utilize allowances and composable fungible tokens can implement the extended trait.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear whether SIP-10 tokens that use tx-sender are compatible with this SIP. Could you elaborate?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I wanted to limit the transfer(from, to, amount) to (transfer (to, amount)) and the sender is always contract-caller, removing first parameter I kept the from parameter to maintain compatibility with current approach.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature is the same but the security model is not, therefore it is not compatible any more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current SIP10 Tokens are only compatibles in that they both will work on Leather Wallet using the same transfer(user,recipient,amount). Also to use a Single DeFi service, no Composability. If you use transfer to use a simple DeFi service like current approaches it will work. Not sure what the Stacks Swap Dapps are doing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improved backwards compatiblity section


## Activation

The activation of this proposal will require deployment to the Stacks blockchain's mainnet. The activation criteria and timeline should be defined in accordance with the Stacks Improvement Proposal process.

## Reference Implementations

Reference implementations of this extended trait should be provided to assist developers in implementing fungible tokens with allowances on the Stacks blockchain. These implementations should follow the specifications outlined in this SIP.

* Trust Machines's [implementation](https://github.com/Trust-Machines/clarity-smart-contracts/blob/main/contracts/composable-fungible-token.clar) (based on [@friedger's](https://github.com/friedger/clarity-smart-contracts/blob/main/contracts/tokens/fungible-token.clar)).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That contract is not sip-10 compatible.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have to improve that.


## Copyright

This SIP is made available under the terms of the Creative Commons CC0 1.0 Universal license.

## Acknowledgments

The author acknowledges Trust Machines and the Stacks community and contributors for their input and feedback in the development of this proposal.