HIP-206: Hedera Token Service Precompiled Contract for Hedera SmartContract Service
Author | Danno Ferrin |
---|---|
Discussions-To | https://github.com/hashgraph/hedera-improvement-proposal/discussions/208 |
Status | Final ⓘ |
Needs Council Approval | Yes ⓘ |
Review period ends ⓘ | Fri, 26 Nov 2021 07:00:00 +0000 |
Type | Standards Track ⓘ |
Category | Service ⓘ |
Created | 2021-11-04 |
Updated | 2022-03-28, 2022-01-10, 2021-11-30, 2021-11-11, 2022-04-26 |
Release | v0.22.0 |
Table of Contents
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) |
Atomic CryptoTransfer
In order to enhance the functionality of the cryptoTransfer
call an alternative form of the call will
be supported with the following ABI signature and hash.
hash | signature | return |
---|---|---|
0x0e71804f |
cryptoTransfer(((address,int64,bool)[]),(address,(address,int64,bool)[],(address,address,int64,bool)[])[]) |
(int) |
This enhanced version of the cryptoTransfer
call supports the atomic transfers of hbars, fungible and non-fungible tokens.
In addition it supports approved fund and token transfers
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 DELEGATECALL
s 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 transactions 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.
Additional Protobuf Fields
Because the child records have an implied transaction body some values that
would be in the transaction call will be part fo the transaction record,
specifically the gas
, amount
, and functionParameters
fields. These fields
only need to be populated when the corresponding and relevant fields are not in
the transaction body.
message ContractFunctionResult {
//...
int64 gas = 10;
int64 amount = 11;
bytes functionParameters = 12;
}
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
- Protobuf Changes - hashgraph/hedera-protobufs PR#120
- Services Implementation - hashgraph/hedera-services PR#2771
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
- Hedera Token Service Developer Docs
- Solidity Support Code
- Response Codes
- Interface ABI Calculations
- Helper Class to avoid Solidity’s EXTCODESIZE check.
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: