快速实现一个闪电贷智能合约

  • 木西
  • 发布于 2025-03-04 15:41
  • 阅读 163

前言本文通过开发、测试、部署闪电贷合约,测试闪电贷合约另外需要代币合约、借款人合约,主要借助openzeppelin相关库的使用,简化开发过程,该合约在Defi领用是非常重要的一部分;闪电贷闪电贷:一种特殊的贷款形式,允许用户在同一个交易中借入大量资金,并在交易结束前归还这些资金,如果用户

前言

本文通过开发、测试、部署闪电贷合约,测试闪电贷合约另外需要代币合约、借款人合约,主要借助openzeppelin相关库的使用,简化开发过程,该合约在Defi领用是非常重要的一部分;

闪电贷

闪电贷:一种特殊的贷款形式,允许用户在同一个交易中借入大量资金,并在交易结束前归还这些资金,如果用户在交易结束前未能归还贷款,整个交易将被回滚,就像从未发生过一样,在去中心化金融(DeFi)领域非常有用;

工作原理

  1. 请求贷款:用户调用闪电贷合约的 flashLoan 函数,请求借入一定数量的代币。
  2. 转移代币:闪电贷合约将代币转移到用户指定的地址。
  3. 执行操作:用户在同一个交易中执行一些操作,如套利或清算。
  4. 归还代币:用户将借入的代币归还给闪电贷合约。
  5. 检查归还:闪电贷合约检查代币是否已归还,如果归还成功,则交易完成;如果归还失败,则交易回滚。

    特点

  6. 无需抵押:用户无需提供任何抵押品即可借入资金。
  7. 原子性:整个交易必须在同一个区块内完成,如果在交易结束前未能归还贷款,整个交易将被回滚。
  8. 高风险高回报:用户可以在同一个交易中进行复杂的操作,如套利和清算,这些操作可能带来高回报,但同时也伴随着高风险。

    用途

  9. 套利:用户可以利用不同交易所或借贷平台之间的价格差异进行套利。例如,用户可以借入一种代币,将其转换为另一种代币,然后在另一个平台上以更高的价格卖出,最后归还借入的代币并保留利润。
  10. 清算:用户可以利用闪电贷来清算其他用户的抵押不足的头寸。例如,如果某个用户的抵押品价值下降,导致其贷款头寸抵押不足,闪电贷用户可以借入资金,清算该头寸,并获得清算奖励。
  11. 流动性提供:用户可以利用闪电贷在不同的流动性池之间转移资金,以获取更高的收益。

合约开发

闪电贷合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import "hardhat/console.sol";

interface IFlashLoanReceiver {
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

contract FlashLoan is ReentrancyGuard {
    using Math for uint256;

    // 闪电贷费率 0.05% = 5/10000
    uint256 public constant FLASH_LOAN_FEE = 5;
    uint256 public constant FLASH_LOAN_FEE_PRECISION = 10000;

    // 闪电贷事件
    event FlashLoan(
        address indexed receiver,
        address indexed token,
        uint256 amount,
        uint256 fee
    );

    /**
     * @dev 执行闪电贷
     * @param receiver 接收闪电贷的合约地址
     * @param token 借贷的代币地址
     * @param amount 借贷金额
     * @param params 附加参数
     */
    function flashLoan(
        address receiver,
        address token,
        uint256 amount,
        bytes calldata params
    ) external nonReentrant {
        require(amount > 0, "FlashLoan: Amount must be greater than 0");

        IERC20 asset = IERC20(token);
        uint256 balanceBefore = asset.balanceOf(address(this));
        require(balanceBefore >= amount, "FlashLoan: Not enough tokens");

        // 计算费用
        uint256 fee = amount.mulDiv(FLASH_LOAN_FEE, FLASH_LOAN_FEE_PRECISION);

        // 转账代币到接收者
        asset.transfer(receiver, amount);

        // 调用接收者的回调函数
        require(
            IFlashLoanReceiver(receiver).executeOperation(
                token,
                amount,
                fee,
                msg.sender,
                params
            ),
            "FlashLoan: Invalid flash loan executor return"
        );

        // 验证还款金额
        uint256 balanceAfter = asset.balanceOf(address(this));
        require(
            balanceAfter >= balanceBefore + fee,
            "FlashLoan: Flash loan not repaid"
        );

        emit FlashLoan(receiver, token, amount, fee);
    }
}
# 编译指令
# npx hardhat compile

代币合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken1 is ERC20 {
    constructor() ERC20("Mock Token", "MTK") {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }
}
# 编译指令
# npx hardhat compile

闪电贷接收者合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract FlashLoanReceiver {
    // 闪电贷回调函数
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        // 在这里执行闪电贷逻辑

        // 还款
        uint256 amountToRepay = amount + premium;
        IERC20(asset).transfer(msg.sender, amountToRepay);

        return true;
    }
}
# 编译指令
# npx hardhat compile

合约测试

测试说明:主要对闪电贷合约,成功调用、余额不足、未贷款、未还款功的测试

const { ethers ,getNamedAccounts,deployments} = require("hardhat");
const { expect } = require("chai");

describe("闪电贷合约测试", function () {
    let flashLoan;
    let mockToken;
    let flashLoanReceiver;
    let owner;
    let addr1;
    let addr2;

    beforeEach(async function () {
        await deployments.fixture(["Token", "FlashLoan", "FlashLoanReceiver"]);
        [owner, addr1, addr2] = await ethers.getSigners();
        // 部署 MockToken
        const tokenDeployment = await deployments.get("MyToken");
        mockToken = await ethers.getContractAt("MyToken",tokenDeployment.address);//已经部署的合约交互
        // 部署 FlashLoan
        const flashLoanDeployment = await deployments.get("FlashLoan");
        flashLoan = await ethers.getContractAt("FlashLoan",flashLoanDeployment.address);//已经部署的合约交互
        // 部署 FlashLoanReceiver
        const flashLoanReceiverDeployment = await deployments.get("FlashLoanReceiver");
        flashLoanReceiver = await ethers.getContractAt("FlashLoanReceiver",flashLoanReceiverDeployment.address);//已经部署的合约交互
        // 转一些代币到 FlashLoan 合约
        await mockToken.transfer(
            await flashLoan.getAddress(),
            ethers.parseEther("1000")
        );
    });

    describe("闪电贷 操作", function () {
        it("闪电贷执行成功", async function () {
            const loanAmount = ethers.parseEther("100");
            const fee = (loanAmount * BigInt(5)) / BigInt(10000); // 0.05% fee

            // 给接收者转一些代币用于支付费用
            await mockToken.transfer(
                await flashLoanReceiver.getAddress(),
                fee
            );

            // 执行闪电贷
            await expect(
                flashLoan.flashLoan(
                    await flashLoanReceiver.getAddress(),
                    await mockToken.getAddress(),
                    loanAmount,
                    "0x"
                )
            )
                .to.emit(flashLoan, "FlashLoan")
                .withArgs(
                    await flashLoanReceiver.getAddress(),
                    await mockToken.getAddress(),
                    loanAmount,
                    fee
                );

            // 验证 FlashLoan 合约的余额
            expect(await mockToken.balanceOf(await flashLoan.getAddress())).to.equal(
                ethers.parseEther("1000") + fee
            );
        });

        it("贷款金额为0 失败", async function () {
            await expect(
                flashLoan.flashLoan(
                    await flashLoanReceiver.getAddress(),
                    await mockToken.getAddress(),
                    0,
                    "0x"
                )
            ).to.be.revertedWith("FlashLoan: Amount must be greater than 0");
        });

        it("token不足 失败", async function () {
            const hugeAmount = ethers.parseEther("10000");
            await expect(
                flashLoan.flashLoan(
                    await flashLoanReceiver.getAddress(),
                    await mockToken.getAddress(),
                    hugeAmount,
                    "0x"
                )
            ).to.be.revertedWith("FlashLoan: Not enough tokens");
        });

        it("贷款未偿还 失败", async function () {
            // 部署一个恶意的接收者合约,它不会还款
            const MaliciousReceiver = await ethers.getContractFactory("FlashLoanReceiver");
            const maliciousReceiver = await MaliciousReceiver.deploy();
            await maliciousReceiver.waitForDeployment();

            // 不给恶意接收者转入费用代币
            await expect(
                flashLoan.flashLoan(
                    await maliciousReceiver.getAddress(),
                    await mockToken.getAddress(),
                    ethers.parseEther("100"),
                    "0x"
                )
            ).to.be.revertedWith("FlashLoan: Flash loan not repaid");
        });
    });
});
# 测试指令
# npx hardhat test ./test/xxx.js

合约部署

闪电贷合约部署

module.exports = async function ({getNamedAccounts,deployments}) {
    const firstAccount = (await getNamedAccounts()).firstAccount;
    const {deploy,log} = deployments;
    const FlashLoan=await deploy("FlashLoan",{
        contract: "FlashLoan",
        from: firstAccount,
        args: [],//参数 owner
        log: true,
        // waitConfirmations: 1,
    })
    console.log("FlashLoan合约地址",FlashLoan.address)
  }
  module.exports.tags = ["all", "FlashLoan"]
# 部署指令
# npx hardhat deploy

代币合约部署

module.exports = async  ({getNamedAccounts,deployments})=>{
    const getNamedAccount = (await getNamedAccounts()).firstAccount;
    // const getNamedAccount = (await getNamedAccounts()).secondAccount;
    console.log('getNamedAccount',getNamedAccount)
    const TokenName = "ETHToken";
    const TokenSymbol = "ETH";
    const {deploy,log} = deployments;
    const Token=await deploy("MyToken1",{
        from:getNamedAccount,
        args: [],//参数
        log: true,
    })
    // await hre.run("verify:verify", {
    //     address: TokenC.address,
    //     constructorArguments: [TokenName, TokenSymbol],
    //     });
    console.log('合约地址1',Token.address)
}
module.exports.tags = ["all", "Token"];
# 部署指令
# npx hardhat deploy

闪电贷接收者合约部署

module.exports = async function ({getNamedAccounts,deployments}) {
    const firstAccount = (await getNamedAccounts()).firstAccount;
    const {deploy,log} = deployments;
    const FlashLoanReceiver=await deploy("FlashLoanReceiver",{
        contract: "FlashLoanReceiver",
        from: firstAccount,
        args: [],//参数 owner
        log: true,
        // waitConfirmations: 1,
    })
    console.log("FlashLoanReceiver合约地址",FlashLoanReceiver.address)
  }
  module.exports.tags = ["all", "FlashLoanReceiver"]
# 部署指令
# npx hardhat deploy

总结

以上就是闪电贷合约的开发、测试、部署以及闪电贷相关概念使用场景等相关介绍,注意:测试中ethers版本为v6,使用v5版本部分方法有所不同,v5中在测试中会出现Cannot read properties of undefined (reading 'provider')相关问题;

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
江湖只有他的大名,没有他的介绍。