{"author":{"address":null,"user":"https://learnblockchain.cn/people/18158"},"content":{"body":"# 前言\r\n\u003e 本文通过开发、测试、部署闪电贷合约,测试闪电贷合约另外需要代币合约、借款人合约，主要借助openzeppelin相关库的使用，简化开发过程，该合约在Defi领用是非常重要的一部分；\r\n# 闪电贷\r\n**闪电贷**：一种特殊的贷款形式，允许用户在同一个交易中借入大量资金，并在交易结束前归还这些资金,如果用户在交易结束前未能归还贷款，整个交易将被回滚，就像从未发生过一样，在去中心化金融（DeFi）领域非常有用；\r\n#### 工作原理\r\n1.  **请求贷款**：用户调用闪电贷合约的 `flashLoan` 函数，请求借入一定数量的代币。\r\n2.  **转移代币**：闪电贷合约将代币转移到用户指定的地址。\r\n3.  **执行操作**：用户在同一个交易中执行一些操作，如套利或清算。\r\n4.  **归还代币**：用户将借入的代币归还给闪电贷合约。\r\n5.  **检查归还**：闪电贷合约检查代币是否已归还，如果归还成功，则交易完成；如果归还失败，则交易回滚。\r\n#### 特点\r\n1.  **无需抵押**：用户无需提供任何抵押品即可借入资金。\r\n2.  **原子性**：整个交易必须在同一个区块内完成，如果在交易结束前未能归还贷款，整个交易将被回滚。\r\n3.  **高风险高回报**：用户可以在同一个交易中进行复杂的操作，如套利和清算，这些操作可能带来高回报，但同时也伴随着高风险。\r\n#### 用途\r\n1.  **套利**：用户可以利用不同交易所或借贷平台之间的价格差异进行套利。例如，用户可以借入一种代币，将其转换为另一种代币，然后在另一个平台上以更高的价格卖出，最后归还借入的代币并保留利润。\r\n2.  **清算**：用户可以利用闪电贷来清算其他用户的抵押不足的头寸。例如，如果某个用户的抵押品价值下降，导致其贷款头寸抵押不足，闪电贷用户可以借入资金，清算该头寸，并获得清算奖励。\r\n3.  **流动性提供**：用户可以利用闪电贷在不同的流动性池之间转移资金，以获取更高的收益。\r\n\r\n# 合约开发\r\n#### 闪电贷合约\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\n\r\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\r\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\r\nimport \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\r\nimport {Math} from \"@openzeppelin/contracts/utils/math/Math.sol\";\r\nimport \"hardhat/console.sol\";\r\n\r\ninterface IFlashLoanReceiver {\r\n    function executeOperation(\r\n        address asset,\r\n        uint256 amount,\r\n        uint256 premium,\r\n        address initiator,\r\n        bytes calldata params\r\n    ) external returns (bool);\r\n}\r\n\r\ncontract FlashLoan is ReentrancyGuard {\r\n    using Math for uint256;\r\n\r\n    // 闪电贷费率 0.05% = 5/10000\r\n    uint256 public constant FLASH_LOAN_FEE = 5;\r\n    uint256 public constant FLASH_LOAN_FEE_PRECISION = 10000;\r\n\r\n    // 闪电贷事件\r\n    event FlashLoan(\r\n        address indexed receiver,\r\n        address indexed token,\r\n        uint256 amount,\r\n        uint256 fee\r\n    );\r\n\r\n    /**\r\n     * @dev 执行闪电贷\r\n     * @param receiver 接收闪电贷的合约地址\r\n     * @param token 借贷的代币地址\r\n     * @param amount 借贷金额\r\n     * @param params 附加参数\r\n     */\r\n    function flashLoan(\r\n        address receiver,\r\n        address token,\r\n        uint256 amount,\r\n        bytes calldata params\r\n    ) external nonReentrant {\r\n        require(amount \u003e 0, \"FlashLoan: Amount must be greater than 0\");\r\n        \r\n        IERC20 asset = IERC20(token);\r\n        uint256 balanceBefore = asset.balanceOf(address(this));\r\n        require(balanceBefore \u003e= amount, \"FlashLoan: Not enough tokens\");\r\n\r\n        // 计算费用\r\n        uint256 fee = amount.mulDiv(FLASH_LOAN_FEE, FLASH_LOAN_FEE_PRECISION);\r\n         \r\n        // 转账代币到接收者\r\n        asset.transfer(receiver, amount);\r\n       \r\n        // 调用接收者的回调函数\r\n        require(\r\n            IFlashLoanReceiver(receiver).executeOperation(\r\n                token,\r\n                amount,\r\n                fee,\r\n                msg.sender,\r\n                params\r\n            ),\r\n            \"FlashLoan: Invalid flash loan executor return\"\r\n        );\r\n\r\n        // 验证还款金额\r\n        uint256 balanceAfter = asset.balanceOf(address(this));\r\n        require(\r\n            balanceAfter \u003e= balanceBefore + fee,\r\n            \"FlashLoan: Flash loan not repaid\"\r\n        );\r\n\r\n        emit FlashLoan(receiver, token, amount, fee);\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n#### 代币合约\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\n\r\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\r\ncontract MyToken1 is ERC20 {\r\n    constructor() ERC20(\"Mock Token\", \"MTK\") {\r\n        _mint(msg.sender, 1000000 * 10 ** decimals());\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n#### 闪电贷接收者合约\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\n\r\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\r\n\r\ncontract FlashLoanReceiver {\r\n    // 闪电贷回调函数\r\n    function executeOperation(\r\n        address asset,\r\n        uint256 amount,\r\n        uint256 premium,\r\n        address initiator,\r\n        bytes calldata params\r\n    ) external returns (bool) {\r\n        // 在这里执行闪电贷逻辑\r\n        \r\n        // 还款\r\n        uint256 amountToRepay = amount + premium;\r\n        IERC20(asset).transfer(msg.sender, amountToRepay);\r\n        \r\n        return true;\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n# 合约测试\r\n**测试说明**：主要对闪电贷合约，成功调用、余额不足、未贷款、未还款功的测试\r\n```\r\nconst { ethers ,getNamedAccounts,deployments} = require(\"hardhat\");\r\nconst { expect } = require(\"chai\");\r\n\r\ndescribe(\"闪电贷合约测试\", function () {\r\n    let flashLoan;\r\n    let mockToken;\r\n    let flashLoanReceiver;\r\n    let owner;\r\n    let addr1;\r\n    let addr2;\r\n\r\n    beforeEach(async function () {\r\n        await deployments.fixture([\"Token\", \"FlashLoan\", \"FlashLoanReceiver\"]);\r\n        [owner, addr1, addr2] = await ethers.getSigners();\r\n        // 部署 MockToken\r\n        const tokenDeployment = await deployments.get(\"MyToken\");\r\n        mockToken = await ethers.getContractAt(\"MyToken\",tokenDeployment.address);//已经部署的合约交互\r\n        // 部署 FlashLoan\r\n        const flashLoanDeployment = await deployments.get(\"FlashLoan\");\r\n        flashLoan = await ethers.getContractAt(\"FlashLoan\",flashLoanDeployment.address);//已经部署的合约交互\r\n        // 部署 FlashLoanReceiver\r\n        const flashLoanReceiverDeployment = await deployments.get(\"FlashLoanReceiver\");\r\n        flashLoanReceiver = await ethers.getContractAt(\"FlashLoanReceiver\",flashLoanReceiverDeployment.address);//已经部署的合约交互\r\n        // 转一些代币到 FlashLoan 合约\r\n        await mockToken.transfer(\r\n            await flashLoan.getAddress(),\r\n            ethers.parseEther(\"1000\")\r\n        );\r\n    });\r\n\r\n    describe(\"闪电贷 操作\", function () {\r\n        it(\"闪电贷执行成功\", async function () {\r\n            const loanAmount = ethers.parseEther(\"100\");\r\n            const fee = (loanAmount * BigInt(5)) / BigInt(10000); // 0.05% fee\r\n\r\n            // 给接收者转一些代币用于支付费用\r\n            await mockToken.transfer(\r\n                await flashLoanReceiver.getAddress(),\r\n                fee\r\n            );\r\n\r\n            // 执行闪电贷\r\n            await expect(\r\n                flashLoan.flashLoan(\r\n                    await flashLoanReceiver.getAddress(),\r\n                    await mockToken.getAddress(),\r\n                    loanAmount,\r\n                    \"0x\"\r\n                )\r\n            )\r\n                .to.emit(flashLoan, \"FlashLoan\")\r\n                .withArgs(\r\n                    await flashLoanReceiver.getAddress(),\r\n                    await mockToken.getAddress(),\r\n                    loanAmount,\r\n                    fee\r\n                );\r\n\r\n            // 验证 FlashLoan 合约的余额\r\n            expect(await mockToken.balanceOf(await flashLoan.getAddress())).to.equal(\r\n                ethers.parseEther(\"1000\") + fee\r\n            );\r\n        });\r\n\r\n        it(\"贷款金额为0 失败\", async function () {\r\n            await expect(\r\n                flashLoan.flashLoan(\r\n                    await flashLoanReceiver.getAddress(),\r\n                    await mockToken.getAddress(),\r\n                    0,\r\n                    \"0x\"\r\n                )\r\n            ).to.be.revertedWith(\"FlashLoan: Amount must be greater than 0\");\r\n        });\r\n\r\n        it(\"token不足 失败\", async function () {\r\n            const hugeAmount = ethers.parseEther(\"10000\");\r\n            await expect(\r\n                flashLoan.flashLoan(\r\n                    await flashLoanReceiver.getAddress(),\r\n                    await mockToken.getAddress(),\r\n                    hugeAmount,\r\n                    \"0x\"\r\n                )\r\n            ).to.be.revertedWith(\"FlashLoan: Not enough tokens\");\r\n        });\r\n\r\n        it(\"贷款未偿还 失败\", async function () {\r\n            // 部署一个恶意的接收者合约，它不会还款\r\n            const MaliciousReceiver = await ethers.getContractFactory(\"FlashLoanReceiver\");\r\n            const maliciousReceiver = await MaliciousReceiver.deploy();\r\n            await maliciousReceiver.waitForDeployment();\r\n\r\n            // 不给恶意接收者转入费用代币\r\n            await expect(\r\n                flashLoan.flashLoan(\r\n                    await maliciousReceiver.getAddress(),\r\n                    await mockToken.getAddress(),\r\n                    ethers.parseEther(\"100\"),\r\n                    \"0x\"\r\n                )\r\n            ).to.be.revertedWith(\"FlashLoan: Flash loan not repaid\");\r\n        });\r\n    });\r\n});\r\n# 测试指令\r\n# npx hardhat test ./test/xxx.js\r\n```\r\n# 合约部署\r\n#### 闪电贷合约部署\r\n```\r\nmodule.exports = async function ({getNamedAccounts,deployments}) {\r\n    const firstAccount = (await getNamedAccounts()).firstAccount;\r\n    const {deploy,log} = deployments;\r\n    const FlashLoan=await deploy(\"FlashLoan\",{\r\n        contract: \"FlashLoan\",\r\n        from: firstAccount,\r\n        args: [],//参数 owner\r\n        log: true,\r\n        // waitConfirmations: 1,\r\n    })\r\n    console.log(\"FlashLoan合约地址\",FlashLoan.address)\r\n  }\r\n  module.exports.tags = [\"all\", \"FlashLoan\"]\r\n# 部署指令\r\n# npx hardhat deploy\r\n```\r\n#### 代币合约部署\r\n```\r\nmodule.exports = async  ({getNamedAccounts,deployments})=\u003e{\r\n    const getNamedAccount = (await getNamedAccounts()).firstAccount;\r\n    // const getNamedAccount = (await getNamedAccounts()).secondAccount;\r\n    console.log('getNamedAccount',getNamedAccount)\r\n    const TokenName = \"ETHToken\";\r\n    const TokenSymbol = \"ETH\";\r\n    const {deploy,log} = deployments;\r\n    const Token=await deploy(\"MyToken1\",{\r\n        from:getNamedAccount,\r\n        args: [],//参数\r\n        log: true,\r\n    })\r\n    // await hre.run(\"verify:verify\", {\r\n    //     address: TokenC.address,\r\n    //     constructorArguments: [TokenName, TokenSymbol],\r\n    //     });\r\n    console.log('合约地址1',Token.address)\r\n}\r\nmodule.exports.tags = [\"all\", \"Token\"];\r\n# 部署指令\r\n# npx hardhat deploy\r\n```\r\n#### 闪电贷接收者合约部署\r\n```\r\nmodule.exports = async function ({getNamedAccounts,deployments}) {\r\n    const firstAccount = (await getNamedAccounts()).firstAccount;\r\n    const {deploy,log} = deployments;\r\n    const FlashLoanReceiver=await deploy(\"FlashLoanReceiver\",{\r\n        contract: \"FlashLoanReceiver\",\r\n        from: firstAccount,\r\n        args: [],//参数 owner\r\n        log: true,\r\n        // waitConfirmations: 1,\r\n    })\r\n    console.log(\"FlashLoanReceiver合约地址\",FlashLoanReceiver.address)\r\n  }\r\n  module.exports.tags = [\"all\", \"FlashLoanReceiver\"]\r\n# 部署指令\r\n# npx hardhat deploy\r\n```\r\n# 总结\r\n以上就是闪电贷合约的开发、测试、部署以及闪电贷相关概念使用场景等相关介绍，`注意`：测试中ethers版本为v6，使用v5版本部分方法有所不同，v5中在测试中会出现Cannot read properties of undefined (reading 'provider')相关问题；","title":"快速实现一个闪电贷智能合约"},"history":null,"timestamp":1741074109,"version":1}