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

atomic assoc contracts #711

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions HIP/hip-0000-atomic-association-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
hip: 0000
title: Optimstic token association for smart contracts
author: Matthew DeLorenzo <[email protected]>, Vae Vecturne <[email protected]>
type: Standards Track
category: Service
needs-council-approval: Yes
status: Draft
created: 2023-04-06
updated: 2023-05-12
discussions-to: https://github.com/hashgraph/hedera-improvement-proposal/discussions/696
---

## Abstract

Smart contracts are required to be associated with all tokens they custody. This makes sense when the contract has a long lived balance > 0; Hedera state is expanded and must be paid for in ongoing maintenance and storage costs to the network. However, there are many use cases that call for smart contracts atomically taking custody of a token, often to transfer to another entity within the same contract call. In this case, association is still required but Hedera state is not expanded.
Copy link
Collaborator

Choose a reason for hiding this comment

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

My perspective here is that if the contract ever held custody of an asset, it should pay the association cost out of sheer fairness to all entities in the network. Doing something else is giving special treatment to contracts when they are just another entity and should be treated as such.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@littletarzan want to make sure you saw this comment ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mgarbs Is it true the association fee is to help pay for expanded network state size? If so, then atomic custody of tokens by smart contracts without association would not add to the net size of the network state. It seems unreasonable to have to pay the association fee to custody a token for an atomic or infinitesimal amount of time.


We propose that association not be required for Hedera entities that only custody a token during an atomic transaction.

## Motivation

Currently, a smart contract attempting to custody an unassociated token will be immediately rejected by the network. This presents a problem in some decentralized finance (DeFi) applications that use smart contracts to atomically custody tokens. Decentraliztion may be achieved by allowing contracts to remain token agnostic, but also prevent spamming attacks by allowing any user to associate tokens to a DeFi smart contract.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the proposal refer to all smart contract regardless of creation path?
For example those created via HAPI ContractCreate have the option of setting max_automatic_token_associations and updating it in a ContractUpdate.

This might be a good section to address why that path is not sufficient for the proposal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This proposal refers to all smart contracts regardless of creation path. Smart contracts are often intermediaries for many different tokens, so it becomes a challenge to maintain association maps and retain decentralization. Presently, dexes are associating/dissociating tokens using precompiles every time they are used as intermediaries. If Hedera was able to refine association checks such that the ending contract balance must be greater than zero, it would eliminate the need to pay 5 cents more per transaction.

We will address why the max_automatic_token_association is not sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Nana-EC this is now addressed on line 24


`MAX_AUTOMATIC_ASSOCIATION` would not be sufficient for technical and some practical reasons. A decentralized contract that mediates transfers between two parties using auto-association slots would have to dissociate the token to free a slot for the next transfer upon completion. However, this process can be corrupted by a third party by sending tokens directly to the contract (without the execution of contract code), thereby requiring another party to dissociate the undesired tokens from the contract.

Another example is the contract caller would need to pay for dissociation of the token each time. For contracts that self-custody a variable number of tokens in a contract call (for example, UniswapV3 Swap Router), the burden of calculating how much gas to send is placed on the front end application. Due to the relatively high cost of associating and dissociating tokens using smart contract precompiles, a comparatively low upper limit is placed on the number of tokens that can be custodied by the contract. If instead tokens could optimistically be associated to contracts, the gas cost of association/disociation is not needed, and contracts are instead bounded primarily by computation and transfer costs.

## Rationale

Periphery contracts for decentralized exchanges (DEXes), such as Uniswap V2 and V3, are designed to atomically custody tokens. For DEXes on Hedera, gas-inefficient workarounds are used to associate and dissociate tokens to periphery smart contracts, paid by end-users.

## Specification

Hedera Smart Contract Service optimistically allows custody of any token for smart contracts, and checks at the end of a contract call that tokens are associated to entities whose balance is changing from zero to nonzero.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this feature be opt-in or opt-out?
In either case if applicable what procedure would a contract creator take?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would like to see this applied to every smart contract. If a contract's token balance changes from zero to nonzero during a contract call transaction, then check for association at the end of the transaction. I would also support opt-in or opt-out if there are use cases that call for it. If an opt-in flag is enabled, and the contract can create another contract using CREATE or CREATE2, then the created contracts should inherit the property from the parent contract.


### Successful

The response code for token transfer of a token not associated to a smart contract, whose balance is equal to zero at the end of the transaction, is SUCCESS = 22.

### Revert

The response code for token transfer of a token not associated to a smart contract, whose balance is nonzero at the end of the transaction, is TOKEN_NOT_ASSOCIATED_TO_ACCOUNT = 184, or a new enum TOKEN_NOT_ASSOCIATED_TO_CONTRACT.

## User stories

As a solidity developer, I do not want to associate tokens to a smart contract that custodies tokens but is not designed to have token balances.

## Backwards Compatibility

This would be backward compatible.

## Security Implications

Incorrect implementation of optimistic token association could result in expansion of global token state for very little cost, resulting in a potentially large storage burden.

## How to Teach This

N/A

## Reference Implementation
#### Example :
Copy link
Contributor

Choose a reason for hiding this comment

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

In the code examples below, I'd recommend:

  • explicitly define the input parameters (token and recipient)
    • e.g. can recipient only be an EOA, or could it be any address, including SCs?
    • e.g. can token only be an HTS token, or could it be any token, including ones authored in Solidity?
    • @param treasury should be @param recipient in the function-level comments
  • in lieu of the comment // assumes msg.sender approved an allowance to address(this), include the code for the approval, as it is more explicit
  • In function1, the return value of the 2nd invocation of HederaTokenService.transferToken is saved in respCode, whereas in function2 this does not happen
    • I assume that you wanted to do so in both, and assert that in function1 it was SUCCESS, and in function2 it should be TOKEN_NOT_ASSOCIATED_TO_ACCOUNT or your newly proposed TOKEN_NOT_ASSOCIATED_TO_CONTRACT
    • If so, add that assertion or a comment; plus consider adding function3 which results in the newly proposed response code.

Example of a transfer transaction that should be allowed:

```
/**
* @dev Example function to transfer entire balance of unassociated tokens
* @param token The HTS token to transfer
* @param treasury The recipient of the token transfer (is associated to token)
*/
function function1(address token, address recipient) external {
require(IERC20(token).balanceOf(address(this)) == 0);
HederaTokenService.dissociateToken(token, address(this));
// assumes msg.sender approved an allowance to address(this)
HederaTokenService.transferToken(token, msg.sender, address(this), 1);
int respCode = HederaTokenService.transferToken(token, address(this), recipient, 1);
assert(respCode == 22);
}
```
Example of a transfer transaction that should not be allowed:

```
/**
* @dev Example of a function call that should fail because address(this) balance is nonzero at end of function call
* @param token The HTS token to transfer
* @param treasury The recipient of the token transfer (is associated to token)
*/
function function2(address token, address recipient) external {
require(IERC20(token).balanceOf(address(this)) == 0);
HederaTokenService.dissociateToken(token, address(this));
// assumes msg.sender approved an allowance to address(this)
HederaTokenService.transferToken(token, msg.sender, address(this), 2);
HederaTokenService.transferToken(token, address(this), recipient, 1);
}
```

## Rejected Ideas

None as of yet.

## Open Issues

None as of yet.

## References

- 1 - https://eips.ethereum.org/EIPS/eip-20
- 2 - https://hips.hedera.com/hip/hip-23
- 3 - https://hips.hedera.com/hip/hip-367
- 4 - https://github.com/LimeChain/HeliSwap-contracts/blob/13ba01b9ed89201888283c56e76a4e266489a7ff/contracts/periphery/UniswapV2Router02.sol#L141
- 5 - https://github.com/Uniswap/v3-periphery/blob/6cce88e63e176af1ddb6cc56e029110289622317/contracts/SwapRouter.sol#L147


## Copyright/license

This document is licensed under the Apache License, Version 2.0 -- see [LICENSE](../LICENSE) or (https://www.apache.org/licenses/LICENSE-2.0)