HIP-206: Hedera Token Service Precompiled Contract for Hedera SmartContract Service Source

AuthorDanno Ferrin
Discussions-Tohttps://github.com/hashgraph/hedera-improvement-proposal/discussions/208
StatusAccepted
Needs Council ApprovalYes
Review period endsFri, 26 Nov 2021 07:00:00 +0000
TypeStandards Track
CategoryService
Created2021-11-04
Updated2022-01-10, 2021-11-11, 2021-11-30

Abstract

Describe the integration of Hedera Token Service (HTS) with the Hedera Smart Contract Service (HSCS), allowing contracts to transfer, mint, burn, associate, and dissociate tokens programmatically.

Motivation

The fusion of the high performance token system in Hedera with programmatic contracts is a frequently requested feature. Specifically, the ability to manage tokens outside the smart contract service as well as via smart contracts.

Rationale

To bridge the EVM and Hedera services it will be necessary to use the EVM’s precompiled contract facility. Because of the integration with Solidity it is also valuable to be able to treat the precompile as a system contract. Hence, a precompile whose input data follows the solidity calling conventions addresses both needs.

There are also 3 perspectives on the HTS System. The first perspective is with the same level of specificity the gRPC calls provide to the mapped service message. A second perspective is of Smart Contract authors trying to make gas-efficient calls. The third perspective is Smart Contract Engineers who want to treat HTS tokens as much as ERC-20 tokens as possible.

There are 5 main calls: transfer, mint, burn, associate, and dissociate. A layer of convince calls with explicit mapping to those main 5 calls will be provided for the second perspective. This third perspective is out of scope for this HIP and will be addressed in a later HIP.

User stories

As a smart contract developer, I want to be able to transfer, mint, burn, associate, and dissociate smart contract tokens through solidity contract calls.

As a smart contract developer, I want my smart contract to be able to transfer, mint, burn, associate, and dissociate tokens where authorization is granted because my smart contract initiated the call and without any other signatures.

Specification

EVM Precompile

The precompile address will be 0x167, decimal 359 (U+0167 is lower case t-bar ‘ŧ’). The precompile input data will follow the calling conventions of Solidity ABI version 2. Version 2 is required to use multidimensional arrays. Only the tokenTransfer method is not compatible with ABI version 1.

Solidity Function Signatures

The Solidity file for development contains the function signatures that the precompile will respond to. It is included in this HIP by reference.

The tokenTransfer message will be supported by five distinct functions. The principal function tokenTransfer use of the ABIv2 multidimensional array encoding and supports the full atomic transfer of multiple token types in one transaction.

To accommodate ABIv1 use four functions with a more focused application are provided. Two party single token transfers are supported by tokenTransferSingle for fungible tokens and tokenTransferNFT for NFTs. Two AccountAmount or a single NFTTransfer structs will be created for these calls.

To do bulk transfers in a single token type the tokenTransferBulk and tokenTransferBulkNFT support fungible and non-fungible transfers between multiple parties. The size of each array for both calls needs to be the same, and each array index represents a separate AccountAmount or NFTTransfer object.

The four remaining functions tokenMint, tokenBurn, associateTokens, and dissociateTokens serve as direct mappings to their respective gRPC calls.

All functions return the responseCode that would be generated by calling their counterparts via gRPC. In addition to the response codes The tokenMint and tokenBurn methods return the new total supply after their operation and tokenMint returns the new NFT serial numbers if new NFTs are generated.

The ABI signature and hashes for each call are as follows

hash signature return
189a554c cryptoTransfer((address,(address,int64)[],(address,address,int64)[])[]) (int)
82bba493 transferTokens(address,address[],int64[]) (int)
eca36917 transferToken(address,address,address,int64) (int)
2c4ba191 transferNFTs(address,address[],address[],int64[]) (int)
5cfc9011 transferNFT(address,address,address,int64) (int)
278e0b88 mintToken(address,uint64,bytes[]) (int,uint64,int[])
acb9cff9 burnToken(address,uint64,int64[]) (int,uint64)
2e63879b associateTokens(address,address[]) (int)
49146bde associateToken(address,address[]) (int)
78b63918 dissociateTokens(address,address[]) (int)
099794e8 dissociateToken(address,address[]) (int)

Precompile Gas Costs

The gas cost of the precompile will be variable based on the specific function called, but will not be less than 1 gas per byte of data in the input data. Gas will be priced so that it will be cheaper to do calls directly to HTS rather than through HSCS where such calls are possible.

Function Base Cost Incremental Cost
Transfers xx Gas xx Gas / Transfer
Mint and Burn xx Gas 0 Gas
Associate and Dissociate xx Gas xx Gas / Token

Transfers are the cryptoTransfer, transferToken, transferTokens, transferNFT, and transferNFTs functions. A transfer is considered an item in either of the NFT or token transfer list. transferToken and transferNFT have 2 transfers implicit in the function call.

Mint and Burn are the mintToken and burnToken functions.

Associate and Dissociate are the associateToken, associateTokens, dissociateToken, and dissociateTokens functions.

Contract Key

To support contracts controlling features like minting and burning the defined but up until now unused field contractID in the Key protobuf message will be utilised. When a token has a contract key as the admin key or one of the admin keys then that key is considered validated if the CALLER of the relevant method is the contract identified in the call. This is the effect of a CALL operation in the EVM.

Delegatable Contract Key

Some smart contracts may want to delegate control to a library contract. In order to allow this and preserve the default security of only permitting direct contract calls to the EVM a second contract key field delegatableContractKey will be added to the Key protobuf message. The requirement for authorization will be that only the CALLER of the call be the authorized contract ID. This will allow contracts to do a DELEGATECALL to the library and then the library (or any of the other sub-libraries it then DELEGATECALLs to) can CALL the precompile contract and have the authorization granted as though it was the origin of the current delegate call chain.

Using a contractKey is the safer options and is recommended as the default contract level security. delegatableContractKey should only be used when the library is fully known and audited ahead of time.

Signature Verification

For operations that require signature verification the signatures of the top level contract call are considered, in addition to the immediate caller of the operation. This may result in a contract call that may need to be signed by more than the initial caller of the smart contract.

In order for a signature to be used for verification, the entire public key needs to be stored in the pubKeyPrefix field of the SignaturePair message. Any partial prefixes provided in the signature map will be ignored and will not properly authorize the contract, even if the signature is valid. This is the current practice of the Hedera supplied SDKs.

Precompile Transaction Records

Whenever a precompile performs an action that is equivalent to a top level transaction a transaction record must be produced in the record stream corresponding to that transaction. This transaction record also needs to be traceable between parent and child. To facilitate this a few fields will be added to the standard protobuf definitions.

TransactionID nonce field

A new nonce field of will be added to the TransactionID message. Each child transaction will use the next sequential value of nonce that is unused.

In addition to use for precompiled contracts this nonce field will be used by scheduled transactions and other future transactions that may have child as part of their design.

There will be an upper limit of 500 child transactions per transaction. If that limit is reached then the remaining precompile calls must fail.

Transaction Record parent_consensus_timestamp field

Each record that is a child transaction will need to populate the parent_consensus_timestamp that has the timestamp of the parent transaction. Transactions that have no parent will not populate this field

Querying child Transactions

Support for querying child transactions will be added to the TransactionGetReceiptQuery and TransactionGetRecordQuery messages via a include_child_receipts and include_child_records boolean field in each query. When that field is set to true the repeated child_transaction_receipts and child_transaciton_records fields in the response message will contain the relevant child transactions for the requested transaction.

REVERTED_SUCCESS ResponseCode

Whenever a precompiled contract succeeds but is later reverted within the smart contract (for example, a REVERT operation was executed or the frame ran out of gas) the return status will be set to REVERT_SUCCESS.

While other fields will be set to non-success values (such as listing no crypto transfers) the contractCallResult and ContractCreateResult field appropriate to the transaciton will need to remain with the values used in the EVM. This is needed to ensure that the smart contract call can be replayed in external programs.

Backwards Compatibility

There is no previous implementation of HTS in the HSCS. There is also no conflict with existing ERC-20s that are EVM only. Migration of existing contracts is out of scope for this HIP.

All the new protobuf fields are optional. The zero/empty value has the appropriate meaning for transactions without child transactions and transactions that are not themselves a child transaction.

Security Implications

Admin Key Call Depth

Controllers of admin keys will need to be careful signing smart contract calls. As their authorization extends to the entire call stack the signers need to make sure that any HTS calls made as a consequence of the initial function call are intended. These calls could be multiple levels deep and crafty contracts can manipulate the calls based on stored state.

Mitigations include relying on contract keys exclusively, using a threshold signature that includes a contract key, only signing contract calls for known and audited sources, and only signing direct HTS calls while relying on a threshold scheme to allow contracts to also perform actions per smart contract rules.

Delegatable Contract Keys and DELEGATECALL

Care needs to be taken writing contracts that are expected to call the HTS precompile contracts via DELEGATECALL into libraries. It is possible to write libraries that can arbitrarily call external code via after-the-fact configuration. Before enabling this key administrators should ensure that only known contracts are called. In many cases these will be contracts authored by the same team, but extra special scrutiny should be given to calling third party contracts.

How to Teach This

SDK tutorials should include examples of how to use the precompile contracts.

Documentation talking about admin keys will need to be updated to address the implication of contract keys, including threshold signatures including contract keys.

Reference Implementation

Current target is 0.21.0.

Rejected Ideas

Making Token Accounts behave like ERC-20 and ERC-721 contracts has been moved outside the scope of this hip.

Open Issues

  • Gas Charged needs to be finalized.

References

Copyright/license

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

Citation

Please cite this document as: