Skip to content

Latest commit

 

History

History
174 lines (141 loc) · 8.52 KB

CREATE_TOKEN.md

File metadata and controls

174 lines (141 loc) · 8.52 KB

How Create and Delete HTS Tokens Work

Hedera Token Services provide several methods to create both Fungible and Non-Fungible Tokens. Each method providing different capabilities for token creation. The following is the list of methods to create tokens.

/// Creates a Fungible Token with the specified properties
/// @param token the basic properties of the token being created
/// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The
/// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible.
/// @param decimals the number of decimal places a token is divisible by
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createFungibleToken(
    HederaToken memory token,
    int64 initialTotalSupply,
    int32 decimals
) external payable returns (int64 responseCode, address tokenAddress);

/// Creates a Fungible Token with the specified properties
/// @param token the basic properties of the token being created
/// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The
/// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible.
/// @param decimals the number of decimal places a token is divisible by.
/// @param fixedFees list of fixed fees to apply to the token
/// @param fractionalFees list of fractional fees to apply to the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createFungibleTokenWithCustomFees(
    HederaToken memory token,
    int64 initialTotalSupply,
    int32 decimals,
    FixedFee[] memory fixedFees,
    FractionalFee[] memory fractionalFees
) external payable returns (int64 responseCode, address tokenAddress);

/// Creates an Non Fungible Unique Token with the specified properties
/// @param token the basic properties of the token being created
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createNonFungibleToken(HederaToken memory token)
    external
    payable
    returns (int64 responseCode, address tokenAddress);

/// Creates an Non Fungible Unique Token with the specified properties
/// @param token the basic properties of the token being created
/// @param fixedFees list of fixed fees to apply to the token
/// @param royaltyFees list of royalty fees to apply to the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createNonFungibleTokenWithCustomFees(
    HederaToken memory token,
    FixedFee[] memory fixedFees,
    RoyaltyFee[] memory royaltyFees
) external payable returns (int64 responseCode, address tokenAddress);

On the other hand, the method to delete an HTS token, either Fungible or Non-Fungible is the following

/// Operation to delete token
/// @param token The token address to be deleted
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function deleteToken(address token) external returns (int64 responseCode);

The main goal of this document is to describe how to enable token creation and deletion on HTS emulation.

What about Forking from a Remote Network?

The introduction of token creation unlocks a new capability: Using the HTS emulation without forking. In order to use HTS emulation, a token must be present in the Ethereum Development Environment network, e.g., Hardhat's EDR or Foundry's Anvil. This can be done in two ways. Fetching the token state from a remote network, or by creating a token locally altogether.

Compare this to the HTS mock present in the Smart Contracts repo. In addition to provide the same features as the HTS mock, we provide forking capabilities as well.

High-level Specification

The following auxiliary definitions are needed. See hashgraph/hedera-smart-contracts#1026 for more details.

interface IHtsFungibleToken is IERC20, IHRC719 {}
interface IHtsNonFungibleToken is IERC721, IHRC719 {}

Create Token

All token creation methods receive a token, which refers to the HederaToken used to configure the initial values of the token being created. They return a tokenAddress, which will be the address of the newly created token.

A successful call to any token creation method should have the following effect

  • The associated bytecode of tokenAddress should be that of the Proxy Contract as defined by HIP719 § Specification. Note that the tokenAddress should be embedded into the bytecode as noted by the HIP.
  • The balance of the token.treasury should be totalSupply.
  • The token.treasury address should be associated to the token.
  • The token should not have any other balances nor associations.
  • A non-fungible token should not have any minted NFT.
  • An immediate call to IHederaTokenService(0x167).getTokenInfo(tokenAddress) should return token.
  • All methods in either IHtsFungibleToken(tokenAddress) or IHtsNonFungibleToken(tokenAddress) should be available to invoke.

Token Creation Counter

HTS emulation should keep an internal counter to track next token address to be created. In forking mode, this counter should resemble the next token address from the remote network. In non-forking mode, this counter should be 0.0.32, the first token account created by local node.

Delete Token

Similarly, a successful call to deleteToken should have the following effect

  • The associated bytecode of tokenAddress should be 0x.
  • No calls to either IHtsFungibleToken(tokenAddress) or IHtsNonFungibleToken(tokenAddress) should be possible.

A deleteToken operation should leave the Token Creation Counter untouched.

Foundry library

A token create method should first check that token is valid, e.g., token.name is not empty. Then a TokenInfo variable should be created containing token and the remaining creation arguments as applicable.

After that, the created TokenInfo should be copied into internal _tokenInfo of the tokenAddress slot space. To avoid changing the redirectForToken and _initTokenData interaction, we can deploy a temporary contract that copies _tokenInfo into its own storage space. After that, we deploy the Proxy Contract bytecode at tokenAddress.

Note

The vm.etch cheatcode does not clean up the storage space of target. That is, the storage space remains unchanged for a given target after vm.etch has been invoked onto that target.

address target = address(0x1234);
vm.store(target, bytes32(uint256(0x0)), bytes32(uint256(1111)));
vm.store(target, bytes32(uint256(0x1)), bytes32(uint256(2222)));

vm.etch(target, address(new C()).code);

C c = C(target);
console.log(c.val1());
console.log(c.val2());
$ foundry test
[...]
Logs:
  1111
  2222
[...]

Hardhat plugin

The main issue with the Hardhat plugin is how to deploy the HIP 719 Proxy Contract into a predefined address, tokenAddress. As expected, this is not possible using only vanilla EVM. As we saw in the previous section, the Foundry library leverages Foundry cheatcode vm.etch to deploy the Proxy Contract to a predefined address. This can be done regardless of the forking environment.

However, the Hardhat plugin works in a completely different manner. If fork is disabled, we simply cannot deploy into a predefined address as mentioned above. We cannot use hardhat_setcode either. This is because there is no way to hook into EVM internal calls (the same reason we had to introduce the JSON-RPC Forwarder in the first place).

A possible solution could be to use the JSON-RPC Forwarder with no backend Mirror Node. Then we can introduce a dummy slot for eth_getStorageAt that signals the creation of a token (see § Token Creation Counter). The JSON-RPC Forwarder can then return the Proxy Contract bytecode when a request eth_getCode(tokenAddress) is performed.

This means that even if the user selects a non-forking environment, Hardhat will run in a forked one, against a no-backend JSON-RPC Forwarder.