HIP-358: Allow Token Create Through Hedera Token Service Precompiled Contract Source

AuthorStoyan Panayotov, Dimitar Dinev
Discussions-Tohttps://github.com/hashgraph/hedera-improvement-proposal/discussions/375
StatusFinal
Needs Council ApprovalYes
Review period endsWed, 30 Mar 2022 07:00:00 +0000
TypeStandards Track
CategoryService
Created2022-02-09
Updated2022-04-01, 2022-04-11, 2022-05-25, 2022-05-26
Releasev0.25.0

Abstract

Describe the Hedera Token Service (HTS) integration with the Hedera Smart Contract Service (HSCS), allowing contracts to create HTS tokens programmatically.

Motivation

Allow smart contract developers on Hedera to create HTS fungible and non-fungible tokens through the HTS precompiled contract giving smart contract developers on Hedera the ability to leverage Hedera native token functionality.

Rationale

HIP-206 already allows token mint, burn, associate, dissociate and transfer through the HTS precompiled contract. Allowing the creation of tokens via smart contracts is a natural extension of the precompiled contract. It would enable smart contract developers to rely solely on contract code for token management.

User stories

As a smart contract developer, I want to be able to create a native HTS fungible token through a solidity contract call.

As a smart contract developer, I want to be able to create a native HTS fungible token with custom fees through a solidity contract call.

As a smart contract developer, I want to be able to create a native HTS non-fungible token through a solidity contract call.

As a smart contract developer, I want to be able to create a native HTS non-fungible token with custom fees through a solidity contract call.

Specification

EVM Precompile extension

The Solidity file for development is updated with new types and function signatures that the precompile will respond to. It is included in this HIP by reference.

New Solidity Types

The following structs have been added to simplify the interface between Solidity and the HTS precompile:

Name Definition
KeyValue (bool, address, bytes, bytes, address)
Expiry (uint, address, uint)
TokenKey (uint, KeyValue)
HederaToken (string, string, address, string, bool, uint32, bool, TokenKey[], Expiry)
FixedFee (uint32, address, bool, bool, address)
FractionalFee (uint32, uint32, uint32, uint32, bool, address)
RoyaltyFee (uint32, uint32, uint32, address, bool, address)

Most of the struct definitions are a straightforward mapping of their corresponding protobuf message. However, there are some notable differences in certain parts, which are explained in-depth in the next several subsections.

TokenKey and KeyValue

The TokenKey struct will be used to define the keys of the newly created token.

Field Purpose
uint keyType The type of token key(s) the specified key in the keyValue field will act as.
KeyValue keyValue The actual key.

Each token key type is mapped to a specific bit in the uint field. To specify key types, the user will set the corresponding bit in the uint. The mappings are as follows:

Bit # Key Type
1 Admin key
2 KYC key
3 Freeze Key
4 Wipe key
5 Supply Key
6 Fee Schedule Key
7 Pause Key
  • A valid TokenKey must have at least one of these bits set and no bit set that does not have a specified meaning (future updates may add more key types).
  • Note that a single TokenKey can be used to define one, multiple or all 7 of the key types.

As already mentioned, the actual key is specified using the KeyValue field. Five different ways of specifying the key are supported in this HIP, each corresponding to a field in the KeyValue struct:

Field Corresponding Key-Value
bool inheritAccountKey if set to true, the key of the calling Hedera account will be inherited as the token key
address contractId smart contract instance that is authorized as if it had signed with a key
bytes ed25519 ED25519 public key bytes
bytes ECDSA_secp256k1 compressed ECDSA(secp256k1) public key bytes (33 byte form)
address delegatableContractId a smart contract that should be treated as having signed if the recipient of the active message frame.

Note that exactly one of these values should be set. Zero or more than one will be considered invalid.

Expiry

As its name suggests, the Expiry struct will serve for defining the expiration schedule of the token:

Field Purpose
uint32 second the epoch second at which the token should expire; if an autoRenewAccount and autoRenewPeriod are specified, this is coerced to the current epoch second plus the autoRenewPeriod
address autoRenewAccount ID of an account which will be automatically charged to renew the token’s expiration, at autoRenewPeriod interval, expressed as a Solidity address
uint32 autoRenewPeriod the interval at which the autoRenewAccount will be charged to extend the token’s expiry

HederaToken

HederaToken struct defines the basic properties of a Hedera Token, both fungible and non-fungible:

Field Purpose
string name The publicly visible name of the token. The token name is specified as a Unicode string.
string symbol The publicly visible token symbol. The token symbol is specified as a Unicode string.
address treasury The account ID will act as a treasury for the token as a solidity address. This account will receive the specified initial supply or the newly minted NFTs in the case of non-fungible token
string memo The memo associated with the token
bool tokenSupplyType Specified the token supply type - false for infinite supply, true for finite
uint32 maxSupply For tokens of type FUNGIBLE_COMMON - the maximum number of tokens in circulation. For tokens of type NON_FUNGIBLE_UNIQUE - the maximum number of NFTs (serial numbers) can be minted.
bool freezeDefault The default Freeze status (frozen or unfrozen) of Hedera accounts relative to this token. If true, an account must be unfrozen before it can receive the token
TokenKey[] tokenKeys The list of keys to set for the token.
Expiry expiry The Expiry struct defines the token’s expiry schedule.

Note that the type of token (fungible or non-fungible) is not specified in the HederaToken struct - it is implied by the specific precompile function being called (see the functions below).

FixedFee

Used to define a fixed fee for the token:

Field Meaning
uint32 amount The amount of units the feeCollector will receive from the specified denomination.
address tokenId Specifies the fixed fee should be denominated in the fungible token with the given id
bool useHbarsForPayment Specifies the fixed fee should be denominated in Hbar
bool useCurrentTokenForPayment Specifies the fixed fee should be denominated in the token currently being created
address feeCollector The ID of the account to receive the custom fee, expressed as a solidity address

Note that the denomination of the fee depends on the values of tokenId, useHbarsForPayment and useCurrentTokenForPayment. Exactly one of the values should be set.

FractionalFee

Used to define a fractional fee for the token:

Field Meaning
uint32 numerator A rational number’s numerator, used to set the amount of a value transfer to collect as a custom fee
uint32 denominator A rational number’s denominator, used to set the amount of a value transfer to collect as a custom fee
uint32 minimumAmount The minimum amount to assess
uint32 maximumAmount The maximum amount to assess (zero implies no maximum)
address feeCollector The ID of the account to receive the custom fee, expressed as a solidity address

RoyaltyFee

Used to define a royalty fee for the token:

Field Meaning
uint32 numerator A fraction’s numerator of fungible value exchanged for an NFT to collect as royalty
uint32 denominator A fraction’s denominator of fungible value exchanged for an NFT to collect as royalty
uint32 amount Populated only when the fallback fee is desired. The amount to charge for the fallback fee
address tokenId Populated only when the fallback fee is desired. Specifies that the fallback fee should be denominated in the fungible token with the given id
bool useHbarsForPayment Populated only when fallback fee is desired. Specifies the fallback fee should be denominated in Hbar
address feeCollector The ID of the account to receive the custom fee, expressed as a solidity address
  • If the user does not want to specify a fallback fee, they must leave amount, tokenId, useHbarsForPayment to their default values
  • If the user wants to specify a fallback fee, they must populate the amount field and exactly one of tokenId and useHbarsForPayment

Solidity Function Signatures

Two functions are defined for creating fungible and non-fungible tokens each. One is a simple version with just the required fields and the other is a full version supporting custom fees. The ABI signature and hashes for each call are as follows:

hash effective signature return
7812a04b createFungibleToken(HederaToken, uint, uint) (int, addess)
4c381ae7 createFungibleTokenWithCustomFees(HederaToken, uint, uint, FixedFee[], FractionalFee[]) (int, addess)
9dc711e0 createNonFungibleToken(HederaToken) (int, addess)
5bc7c0e6 createNonFungibleTokenWithCustomFees(HederaToken, FixedFee[], RoyaltyFee[]) (int, addess)

Precompile Gas Costs

To allow creation of tokens through the precompiled contract, a new mechanism for payment is introduced - precompile calls are partially paid by Hbar value transfers. There is a base cost of 100,000 Gas for each call to the create functions. Additional Hbar cost must be transferred as value to the precompile call. The Hbar price will be calculated using the following formula:

The total cost of HTS Token create through the precompile is equal to the cost of a HAPI Create Token Transaction with similar arguments at that instance plus a 20% premium. The Hbars required to be transferred as value in the precompile call will be equal to the total cost required minus the Hbar cost for the base 100,000 gas cost converted into hbar according to the current gas price.

The gas cost is always charged to the HAPI payer. The Hbars transferred as value to the precompile will be charged to the msg.sender for the message frame. Contracts performing a direct call to the precompile will be required to hold Hbar.

Exchange rates might change and consequently Hbar cost for token create might change. The demo contract provided has the means to adjust how much Hbar is transferred as value to the precompile call.

Minimum Gas Charge

The gas limit in Ethereum fulfills an essential role in limiting processing throughput, and it also serves in Ethereum Mainnet as a long-term storage cost. This HIP only separates the long-term storage costs but needs to keep the throughput limiting. Hence some of the precompile costs must be paid in gas.

Only accepting the exact amount

When a user transfers in via value hbar only the amount needed to pay the transaction is deducted, even if there is excess. This will simplify accounting in smart contacts that want to maximize hbar payment ability and not needing to program in tracking fluctuating exchange rates.

Precompile Transaction Records

Child records for Token Create will be created for each precompile call.

Backwards Compatibility

There are no changes to the protobufs. The changes in the existing HTS precompile only add new features and capabilities and should not impact existing behavior.

Security Implications

This HIP does not change the implementation of HTS token create, nor does it add new features. What is being added is a new way to call existing functionality. The security posture of the system is expected to remain unchanged.

How to Teach This

The IHederaTokenService.sol interface is well documented and example client code is provided with TokenCreateContract.sol.

Reference Implementation

Rejected Ideas

Support for Complex Keys

This HIP only supports simple 1 of 1 and contract keys for authorization and does not support threshold or other complex key structures. There are multiple reasons. First, solidity does not allow for recursive structure definitions like protobufs do. This recursive definition is essential to the complex key structures supported by the protobuf APIs. Second, allowing even one level of threshold signatures increases the size of the ABI significantly for a feature that would burden uses of the API that do not use threshold key arrangements. It is possible that HTS update calls may be added to the HTS precompile, and it would partially address this shortcoming. In the interim, the protbuf API provides full access to complex keys.

Open Issues

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: