hashgraph / hedera-services

Crypto, token, consensus, file, and smart contract services for the Hedera public ledger
Apache License 2.0
281 stars 125 forks source link

Unable to transfer NFTs from a Smart Contract when a fallback fee is present. #12222

Open kantorcodes opened 6 months ago

kantorcodes commented 6 months ago

Description

There no viable way for smart contracts to actually pay fallback fees on an NFT that has one set (eg 100 HBAR). It seems that without this, there is no way to practical way to send an NFT with a fallback fee to an Account ID unless you execute an atomic transaction with cryptoTransfer involving a fungible token, which is super impractical and hacky. This feels like a bug.

I've attached this transaction where an allowance is set on both the Contract and the user for 200k HBAR, but the transaction returns SPENDER_DOES_NOT_HAVE_ALLOWANCE (which is also very unclear in the context)

https://hashscan.io/testnet/transaction/1710853927.371841027

Utilizing other methods like IERC721(tokenAddress).transferFrom(...) also yields weird results.

https://hashscan.io/testnet/transaction/1710866571.915902005

Steps to reproduce

  1. Create a simple smart contract that exposes a payable method to transfer an NFT into the payers account.
  2. Transfer NFTs into the smart contract
  3. Execute the function

Note the code below is pseudo code, you will need to clean it up to actually work.

address private constant PRECOMPILE_ADDRESS = address(0x167);

 /// Initiates a Non-Fungable Token Transfer
    /// @param token The ID of the token as a solidity address
    /// @param sender the sender of an nft
    /// @param receiver the receiver of the nft sent by the same index at sender
    /// @param serialNumber the serial number of the nft sent by the same index at sender
    function transferNFTs(
        address token,
        address[] memory sender,
        address[] memory receiver,
        int64[] memory serialNumber
    ) internal returns (int256 responseCode) {
        (bool success, bytes memory result) = PRECOMPILE_ADDRESS.call(
            abi.encodeWithSelector(
                IHederaTokenService.transferNFTs.selector,
                token,
                sender,
                receiver,
                serialNumber
            )
        );
        responseCode = success
            ? abi.decode(result, (int32))
            : HederaResponseCodes.UNKNOWN;
    }

        function transferFrom(
        address to,
        int64[] memory serialNumbers
    ) external returns (int256) {
        address[] memory senderArray = generateAddressArrayForHTS(address(this), serialNumbers.length);

        address[] memory minterArray = generateAddressArrayForHTS(
            to,
            serialNumbers.length
        );

        int256 responseCode = HederaTokenService.transferNFTs(
            tokenAddress,
            senderArray,
            minterArray,
            serialNumbers
        );

        if (responseCode != HederaResponseCodes.SUCCESS) {
            revert;
        }

        return responseCode;
    }

Additional context

No response

Hedera network

mainnet, testnet

Version

latest

Operating system

None

Nana-EC commented 5 months ago

Hi @kantorcodes Thanks for the ticket and I appreciate the additional context for the flow.

Fallback fees were designed prior to HSCS v2.0 in which we begun to expose native HTS functionality to smart contracts. https://hips.hedera.com/hip/hip-18 define fallback fees to requires a user to send HBAR value to receive an NFT. In accordance with the networks security model a signature, prior approval or additional authority needs to be given to allow value be transferred from the users accounts. This leaves limited options for a flow like a smart contract execution that does not utilize the signatures of a HAPI transaction.

The options include

I would recommend you take a look at HIP 906 as it provides a pathway to one of those solutions. With HIP 906 a user who intends to receive an NFT with a fallback fee could provide an HBAR allowance to a smart contract in advance of the transfer. This would support a successful transfer and is similar to token logic and allowances and approvals. Could you kindly review that HIP and leave a comment there to your scenario and if it helps