Best Practice of Transferring Assets to Smart Contracts

TRON Core Devs
TRON
Published in
12 min readMay 8, 2020

I. Background

A common use case in smart contract development and implementation is to transfer assets to the contracts. This functionality can be achieved in many ways, each leading to different initial targets. As TRON and Solidity version keep iterating, some of these methods may become outdated or even risky. Developers may take different implementation approaches based on their knowledge and experience about TRON smart contracts and TVM. Sometimes the result may fail their expectation if developers go the wrong way. This is common in the developer community. This article will walk you through the correct steps to transfer TRX or TRC10 tokens to deployed contracts. The article starts with a brief introduction to smart contract basics. The main part is on how to transfer assets to deployed contracts. At last, it touches upon how to transfer assets via system contracts, TIP37, and its possible side effect. We hope that this article will help you better understand the ways to transfer assets to smart contracts on TRON.

II. Preparatory Knowledge

Transferring assets to smart contracts involve a wide range of knowledge. In this section, we will mention the five most important terms for this article. You can skip this part if you’ve already learned about them.

2.1 Account Type

TRON employs an Account Balance model. The only identification for an account is the address. Each account owns TRX, Token, bandwidth, Energy, and other resources. An account can send transactions to add/reduce TRX or TRC10 token balance (which consumes bandwidth), deploy smart contracts, trigger the smart contracts which deployed by others, etc. The account is at the center of all actions on TRON.

TRON now supports two account types, normal account, and smart contract account. They differ is that smart contract accounts contain bytecodes that can be executed by TVM.

2.2 Asset Type

There are three types of assets on TRON: TRX, TRC10, and TRC20

  • TRX is the name of the currency used in the TRON network.
  • TRC10 is the issued by system contract, TRC-10 is a technical token standard supported by the TRON blockchain natively without the TRON Virtual Machine (TVM).
  • TRC20 is the token released in accordance with TRON smart contract technical standard. Smart contracts run in TRON virtual machine (TVM). It is fully compatible with ERC20.

2.3 System Contract & Smart Contract

There are two contract types on TRON: system contract and smart contract. TRON smart contracts are written in Solidity (compatible with Ethereum), compiled to bytecode after being written and tested, and then deployed onto blockchain. Smart contracts are executed in TVM, The deployed smart contracts can be retrieved by their addresses. The contract ABI shows all callable functions of the contract. A large number of DApps are implemented based on TRON smart contracts. TRON also supports system contracts. Unlike smart contracts, system contracts are preset TRON contracts that can be called directly by developers. TRC10 is a system-contract-based token. It’s worth noting here that executing system contracts does not involve TVM. The absence of TVM in this process causes much confusion because changes made by the interaction between system contracts and smart contracts cannot be correctly recorded. As some developers who used to work on Ethereum do not know it, they just transfer assets directly via system contracts to smart contracts and run into many problems (On Ethereum, transferring assets to contracts will always trigger fallback function in the contract).

2.4 fallback function

fallback is a special function in smart contracts. One contract can have a maximum of only one fallback function. This function has no parameters and cannot have a return value. It also must be external. The function makes declarations in the form of fallback () external [payable] (without function keyword here). If a non-existing function is called, or if no data was supplied at all when triggering the smart contract, or when address built-in functions (transfer() and send() ) are directly used, the fallback function of the target contract will be executed. This feature is used by many developers to record users' transfers. When fallback is being executed, it will accept all inbound data. If thefallback function is payable, it will be able to receive TRX or TRC10.

2.5 Transferring TRX or TRC10 to non-existent addresses will automatically activate target accounts

In pre-v3.6.5 java-tron Odyssey versions, when using GRPC API CreateTransaction (TransferContract) or TransferAsset to transfer TRX or TRC10 to a non-existent address, the target address will be automatically activated. However, in smart contracts, transferring assets via transfer or transferToken will lead to failed transactions. This inconsistency caused inconvenience for developers. Therefore, in Odyssey v3.6.5, a new mechanism was introduced. When the contract uses transfer, send, transfertoken to transfer TRX or TRC10 tokens to non-existent addresses, the target address will be automatically activated. The process will consume 10000 Energy, which is worth 0.1 TRX. For more information, please visit https://github.com/tronprotocol/tips/issues/54

III Trigger the payable function in smart contracts to transfer assets to contracts

In real-life scenarios, smart contracts should provide the deposit function and withdrawal function which can be used to transfer assets in and out. The correct way of transferring assets to a contract is to call the deposit function of that contract. Similarly, users can call the withdrawal function to withdraw their assets from the contract. All transferring(executed successfully) via calling smart contract functions will be recorded in contract storage. Due to multiple reasons, many deployed smart contracts did not provide such deposit function or withdrawal function, and this article will focus on how to correctly move your assets around to this kind of contract.

Before diving in, let’s talk about TRC20 tokens. TRON’s TRC20 Token Standard sets a protocol for TRC20 Token transfers. Namely, all TRC20 transfers can only be done by triggering smart contracts. All transfers will be recorded in the storage of smart contracts. Therefore, the TRC20 token transfer is more user-friendly.

In terms of transferring TRX and TRC10 into smart contracts, other than triggering smart contracts, users can also use system contracts. Their execution does not go through TVM. It only consumes bandwidth and won’t trigger any functions of the contract. Therefore, the TRX or TRC10 tokens transferred into the smart contracts using this method will not be recorded in the storage of the smart contracts, which could give rise to more issues and traps. Next, we will talk about how to transfer TRX and TRC10 correctly into the contracts.

How do we know if the transferring method is correct? The key is to see if the transfer can be recorded correctly by smart contracts. All methods that can get transfers accurately recorded are correct. Namely, the transferring process must go through the functions in the contracts. Meanwhile, this function must be able to receive TRX/TRC10, which means the function must be payable. Any changes resulted from triggering payable functions will be recorded. In theory, any payable function in a smart contract can receive assets, and users can call payable functions to conduct their desired amount of transfer. If they only want to complete the transfer without triggering any other functions, then calling the payable fallback function is the way to go. In use, there are two common ways of triggering the fallback function.

3.1 Trigger the smart contract when there are non-existing functions or when no data is supplied

Prerequisite:The smart contract must have defined a payable fallback function.

Supported asset type:Both TRX and TRC10 transfers are supported.

How it works:When calling non-existing functions or if no data is supplied when triggering a smart contract, TVM will automatically call the fallback function to execute the command according to its properties. With payable fallback, users are able to complete their transfers of a certain amount of TRX/TRC10. Smart contracts are executed in TVM, so these transfers will eventually be recorded in the storage of contracts.

Steps:Use triggersmartcontract to trigger a smart contract when there are non-existing functions or if no data is supplied at all. Determine the amount of TRX using call_value and that of TRC10 using call_token_value.

Limitation:To use this feature, the fallback function must be payable.

3.2 Transferring via address built-in functions

Prerequisite:The smart contract must have defined a payable fallback function.

Supported asset type:Only TRX transfers are supported.

How it works:There are three address built-in functions supporting TRX transfers: address.send(), address.transfer(), and address.call.value()(). The way they work are similar to the first method introduced above: when calling the three functions, the fallback function in smart contracts will be triggered.

Step:Though the above three functions can all successfully trigger fallback, the issue of Energy is notable. Both transfer and send functions are only given 2300 Energy, while reading (SSLOAD command), modifying (SSTORE command), and creating (SSTORE command) a 32 bytes data require 400, 5000 and 20000 Energy respectively, meaning the 2300 Energy is not sufficient to create and modify the data. In another word, transfer and send with a fallback function which requires more than 2300 Energy are doomed to fail. So you must make sure the fallback function consumes no more than 2300 Energy in order to successfully transfer TRX to contracts via address transfer and send. On top of Energy, following are some other issues to note:

  1. address.send(amount)

Two things to keep in mind when using address.send(): first, as mentioned above, it provides only 2300 Energy; two, the failed send function will only be returned as false and will not throw any exception. Therefore, send should be called along with require to avoid the situation where the assets remain unchanged after the transaction is recorded on-chain and fees are paid.

2. address.transfer(amount)

address.transfer() works in the same way as require(address.send()). There are also two things to keep in mind when using transfer: first, transfer provides only 2300 Energy like send; second, unlike send, transfer features a more secure mechanism where exceptions will be thrown and all completed operations will be revoked when an operation fails.

3. address.call.value(amount)()

address.call.value(amount)( ) is more flexible and adaptable in use compared with the previous two. The reason is that this function provides an interface that allows customization of Energy amount, going beyond the 2300 Energy limitation and leaving room for more complex operations. Still, there are two things to keep in mind when utilizing this function: first, just like send(), this function will only return the false value without throwing an exception upon failed execution, meaning users need to manually process the return value. It is recommended to use require along with this function. Second, if not specify a customized value, the default Energy amount set is the user's all available Energy. The amount of Energy can be modified via .gas(energyLimit). Here is an example:someAddress.call.value(trxAmount).gas(energyAmount)()

Limitation:The above three means can all execute TRX transfers and all have their own advantages and limitations in use. In practice, one needs to choose the most suitable function based on actual needs.

Above are the correct ways to transfer assets to smart contracts. You may have spotted that the two approaches we mentioned share a common prerequisite: the smart contract must have defined a payable fallback function. But in practice, many deployed smart contracts didn’t define payable fallback functions for a variety of reasons. So how to address contracts of this kind? You'll find the answer below.

IV Transferring assets to smart contracts using system contracts

In this section, we will introduce how to transfer assets via TRON’s system contracts. TRON’s system contracts include two contracts for transferring TRX and TRC10, with TransferContract used for TRX transfers and TransferAssetContract for TRC10. These two contracts can be used to transferring assets to both normal accounts and smart contract accounts. System contract consume bandwith only, and does not involve the TVM, nor does it trigger any function in the contract, so such transfer will not be correctly recorded in the contract storage. This results in a situation where the asset balance in the contract changes but such change is not reflected in its storage and content. To users, their assets are successfully transferred to smart contracts (contract storage doesn't change as the smart contract is not correctly triggered), but the transfer is not correctly recorded. For those smart contracts which support asset withdrawal, users are not entitled to withdraw their asset as it is not correctly recorded in the contract. And users' assets will be permanently locked in contracts that do not support asset withdrawal. With the above two system contracts, making transfers to normal accounts does not involve any risk. However, it will be a different case when transferring assets to smart contract accounts, which may result in asset loss. So users are recommended to remain prudent when making transfers to smart contracts using the above two system contracts. The aforementioned issues are constraints of the two system contracts, these 2 system contracts are defined as follows:

TransferContract :

message TransferContract { bytes owner_address = 1; bytes to_address = 2; int64 amount = 3; }//owner_address:contract holder's address. to_address: target account address. amount:amount of the transfer, in units of sun.

TransferAssetContract

message TransferAssetContract { bytes asset_name = 1; bytes owner_address = 2; bytes to_address = 3; int64 amount = 4; }

//asset_name:ID of the listed Token. owner_address:contract holder's address. to_address: target account address. amount:number of Token transferred.

V. Changes introduced by TIP37

In light of the specificity of system contracts and the many reports received on assets lost from users, in TIP37, TRON proposes to ban TransferContract and TransferAssetContract from making transfers to smart contract accounts. Please note that only transfers to smart contract accounts are banned, while transfers to normal accounts are still available. TIP37 proposes to add validate() function in the two system contracts to validate the legitimacy of the toAddress before executing the contracts. Since the proposal was raised, it has drawn significant attention and spirited discussion among the community. Once TIP37 comes into effect, Transferring assets to smart contracts using the two system contracts will be disabled. Some of the anticipated changes with TIP37 are as follows:

  • Deployed contracts that have no payable fallback are not able to receive TRX/TRC10 deposits using the aforementioned methods.
  • New approaches need to be explored for TronLink and similar wallets to transfer TRX/TRC10 to smart contract accounts (if previous transfers were made through system contracts).
  • DApp developers need to update related codes and come up with new ways to transfer assets to contracts (if previous transfers were made through system contracts).
  • TRONWeb will need similar changes.

For deployed contracts that feature no payable fallback function, what are some other methods of transferring assets to smart contracts after TIP37 comes into effect? Here we would like to introduce two of them.

5.1 selfdestruct/suicide

Selfdestruct is a pre-set operation in solidity that destructs the current contract and transfers balance to an address designated by parameters. This feature deletes all bytecodes on the contract address, clears all storage status variables, and restores balance to zero. If the parameter address is a contract address, the operation will not activate any function of the contract. Therefore, selfdestruct may enforce the transfer of TRX/TRC10 to the target contact regardless of its code. Declaration of selfdestruct: selfdestruct(address recipient): Please note that this declaration will not trigger the fallback function.

With the special function selfdestruct, we can deploy new contracts to call their selfdestruct to deposit. This method of deposit may be used on smart contracts who do not have a payable fallback function. Reference code for new contracts is as follows:

// solidity source code
pragma solidity 0.5.8;
contract ForceTransfer{
constructor(address payable toAddress) public payable{
selfdestruct(toAddress);
}
}
// compiling parameter: --optimize --optimize-runs=200
// Compiler: TRON Compiler 0.5.8
https://github.com/tronprotocol/solidity/releases
// Binary: 6080604052604051602080603083398101806040526020811015602157600080fd5b50516001600160a01b038116fffe

Contract JSON ABI

[{"inputs":[{"name":"toAddress","type":"address"}],"payable":true,"stateMutability":"payable","type":"constructor"}]

Developers can implement a forced transfer to target address TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy (may not be real, only an example) according to the following instructions:

  • Transfer y z TRC10 to target contract address TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy
deploycontract ForceTransfer [] 6080604052604051602080603083398101806040526020811015602157600080fd5b50516001600160a01b038116fffe ForceTransfer(address) "TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy" false 10000000 0 10000000 x y z
  • Transfer x TRX to target contract address TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy
deploycontract ForceTransfer [] 6080604052604051602080603083398101806040526020811015602157600080fd5b50516001600160a01b038116fffe ForceTransfer(address) "TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy" false 10000000 0 10000000 x 0 #
  • Transfer x TRX and y z TRC10 to target contract address TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy
deploycontract ForceTransfer [] 6080604052604051602080603083398101806040526020811015602157600080fd5b50516001600160a01b038116fffe ForceTransfer(address) "TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy" false 10000000 0 10000000 0 y z

5.2 A special method introduced by create2

Currently, TRON generates smart contract addresses with two methods. The first type, unpredictable addresses, are created by executing deployContract, or using create in solidity code. This method is based on a calculation of the hash value of the current transaction with a fixed formula because transactions contain timestamps. So addresses generated are random and unpredictable. In comparison, create2 introduces a new method for generating predictable addresses. Create2 is now supported on both TRON Compiler 0.5.4 and java-tron Odyssey-v3.6.0. In the generating process, the same address, salt, and init_code produce the same address. This is crucial for off-chain scaling, because, for instance, participants in off-chain computation may decide upon the contract address to save the time spent on contract deployment. Meanwhile, predictable contract addresses pose potential safety risks, of course. But for this article, we will not elaborate on create2. If you are interested, you may look it up or follow us on future articles.

Ⅵ. Summary

This article introduces the method and principle of transferring assets to deployed smart contracts, together with other existing transfer methods. In the upcoming articles, we will introduce smart contract DelegateCall and how to develop upgradable smart contracts, please stay tuned.

Ⅶ. References

https://solidity.readthedocs.io/en/v0.5.12/index.html

https://github.com/tronprotocol/tips/blob/master/tip-37.md

https://ethereum.stackexchange.com/questions/19341/address-send-vs-address-transfer-best-practice-usage/19343#19343

Ⅷ. For more information

Github: https://github.com/tronprotocol

Telegram: https://t.me/troncoredevscommunity

--

--