Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP: NFT Flashloans #6682

Merged
merged 15 commits into from
Jul 28, 2023
159 changes: 159 additions & 0 deletions EIPS/eip-6682.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
eip: 6682
title: NFT Flashloans
description: Minimal interface for ERC-721 NFT flashloans
author: out.eth (@outdoteth)
discussions-to: https://ethereum-magicians.org/t/eip-6682-nft-flashloans/13294
status: Draft
type: Standards Track
category: ERC
created: 2023-03-12
requires: 20, 721, 3156
---

## Abstract

This standard is an extension of the existing flashloan standard ([ERC-3156](./eip-3156.md)) to support [ERC-721](./eip-721.md) NFT flashloans. It proposes a way for flashloan providers to lend NFTs to contracts, with the condition that the loan is repaid in the same transaction along with some fee.

## Motivation

The current flashloan standard, [ERC-3156](./eip-3156.md), only supports [ERC-20](./eip-20.md) tokens. ERC-721 tokens are sufficiently different from ERC-20 tokens that they require an extension of this existing standard to support them.
SamWilsn marked this conversation as resolved.
Show resolved Hide resolved

An NFT flash loan could be useful in any action where NFT ownership is checked. For example, claiming airdrops, claiming staking rewards, or taking an in-game action such as claiming farmed resources.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

### Contract Interface

```solidity
pragma solidity ^0.8.19;

interface IERC6682 {
/// @dev The address of the token used to pay flash loan fees.
function flashFeeToken() external view returns (address);

/// @dev Whether or not the NFT is available for a flash loan.
/// @param token The address of the NFT contract.
/// @param tokenId The ID of the NFT.
function availableForFlashLoan(address token, uint256 tokenId) external view returns (bool);
}
```

The `flashFeeToken` function MUST return the address of the token used to pay flash loan fees.

If the token used to pay the flash loan fees is ETH then `flashFeeToken` MUST return `address(0)`.

The `availableForFlashLoan` function MUST return whether or not the `tokenId` of `token` is available for a flashloan. If the `tokenId` is not currently available for a flashloan `availableForFlashLoan` MUST return `false` instead of reverting.

SamWilsn marked this conversation as resolved.
Show resolved Hide resolved
Implementers `MUST` also implement `IERC3156FlashLender`.

## Rationale

The above modifications are the simplest possible additions to the existing flashloan standard to support NFTs.

We choose to extend as much of the existing flashloan standard ([ERC-3156](./eip-3156.md)) as possible instead of creating a wholly new standard because the flashloan standard is already widely adopted and few changes are required to support NFTs.

In most cases, the handling of fee payments will be desired to be paid in a separate currency to the loaned NFTs because NFTs themselves cannot always be fractionalized. Consider the following example where the flashloan provider charges a 0.1 ETH fee on each NFT that is flashloaned; The interface must provide methods that allow the borrower to determine the fee rate on each NFT and also the currency that the fee should be paid in.

## Backwards Compatibility

This EIP is fully backwards compatible with [ERC-3156](./eip-3156.md) with the exception of the `maxFlashLoan` method. This method does not make sense within the context of NFTs because NFTs are not fungible. However it is part of the existing flashloan standard and so it is not possible to remove it without breaking backwards compatibility. It is RECOMMENDED that any contract implementing this EIP without the intention of supporting ERC-20 flashloans should always return `1` from `maxFlashLoan`. The `1` reflects the fact that only one NFT can be flashloaned per `flashLoan` call. For example:

```solidity
function maxFlashLoan(address token) public pure override returns (uint256) {
// if a contract also supports flash loans for ERC20 tokens then it can
// return some value here instead of 1
return 1;
}
```

## Reference Implementation

```solidity
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.19;

import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";
import "../interfaces/IERC3156FlashBorrower.sol";
import "../interfaces/IERC3156FlashLender.sol";
import "../interfaces/IERC6682.sol";

contract ExampleFlashLender is IERC6682, IERC3156FlashLender {
uint256 internal _feePerNFT;
address internal _flashFeeToken;

constructor(uint256 feePerNFT_, address flashFeeToken_) {
_feePerNFT = feePerNFT_;
_flashFeeToken = flashFeeToken_;
}

function flashFeeToken() public view returns (address) {
return _flashFeeToken;
}

function availableForFlashLoan(address token, uint256 tokenId) public view returns (bool) {
// return if the NFT is owned by this contract
try IERC721(token).ownerOf(tokenId) returns (address result) {
return result == address(this);
} catch {
return false;
}
}

function flashFee(address token, uint256 tokenId) public view returns (uint256) {
return _feePerNFT;
}

function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 tokenId, bytes calldata data)
public
returns (bool)
{
// check that the NFT is available for a flash loan
require(availableForFlashLoan(token, tokenId), "IERC6682: NFT not available for flash loan");

// transfer the NFT to the borrower
IERC721(token).safeTransferFrom(address(this), address(receiver), tokenId);

// calculate the fee
uint256 fee = flashFee(token, tokenId);

// call the borrower
bool success =
receiver.onFlashLoan(msg.sender, token, tokenId, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan");

// check that flashloan was successful
require(success, "IERC6682: Flash loan failed");

// check that the NFT was returned by the borrower
require(IERC721(token).ownerOf(tokenId) == address(this), "IERC6682: NFT not returned by borrower");

// transfer the fee from the borrower
IERC20(flashFeeToken()).transferFrom(msg.sender, address(this), fee);

return success;
}

function maxFlashLoan(address token) public pure override returns (uint256) {
// if a contract also supports flash loans for ERC20 tokens then it can
// return some value here instead of 1
return 1;
}

function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) {
return this.onERC721Received.selector;
}
}
```

## Security Considerations

It's possible that the `flashFeeToken` method could return a malicious contract. Borrowers who intend to call the address that is returned from the `flashFeeToken` method should take care to ensure that the contract is not malicious. One way they could do this is by verifying that the returned address from `flashFeeToken` matches that of a user input.

Needs discussion.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).