前言本文聚焦DeFi领域的闪电贷智能合约,系统梳理了其定义内涵、核心功能、痛点解决价值及行业落地场景;同时结合HardhatV3开发框架与OpenZeppelin工具库,完整呈现了闪电贷智能合约从开发、测试到部署上线的全流程实践方案。一、DeFi闪电贷是什么去中心化金融协议提供的无
本文聚焦DeFi 领域的闪电贷智能合约,系统梳理了其定义内涵、核心功能、痛点解决价值及行业落地场景;同时结合Hardhat V3 开发框架与OpenZeppelin 工具库,完整呈现了闪电贷智能合约从开发、测试到部署上线的全流程实践方案。
一、DeFi闪电贷是什么
去中心化金融协议提供的无抵押贷款,基于区块链交易原子性,借贷与还款必须在同一笔交易中完成,未按时还款则交易回滚,无需抵押、无信用审核,仅需支付少量手续费
二、能做什么
| 类别 | 核心功能 | 具体场景 |
|---|---|---|
| DeFi 闪电贷 | 套利;清算;债务 / 资产迁移;流动性管理 | 跨平台 / 协议价格套利;清算抵押不足头寸赚奖励;跨协议债务转换、资产迁移;流动性池资金转移优化收益 |
# 编译指令
npx hardhat compile
# 测试指令
npx hardhat test ./scripts/xxx.ts
# 部署指令
npx hardhat run ./test/xxx.ts
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.5.0
pragma solidity ^0.8.24;import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract BoykaYuriToken is ERC20, ERC20Burnable, Ownable, ERC20Permit { constructor(address recipient, address initialOwner) ERC20("MyToken", "MTK") Ownable(initialOwner) ERC20Permit("MyToken") { _mint(recipient, 1000000 * 10 ** decimals()); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } }
### 2.闪电贷合约
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
interface IFlashLoanReceiver { function executeOperation( address asset, uint256 amount, uint256 premium, address initiator, bytes calldata params ) external returns (bool); }
contract FlashLoan is ReentrancyGuard, Ownable { using SafeERC20 for IERC20;
// 费率分母保持 10000,当前 0.05%
uint256 public flashLoanFee = 5;
uint256 public constant FEE_PRECISION = 10000;
error InsufficientLiquidity();
error TransferFailed();
error RepaymentFailed();
error InvalidReceiver();
event FlashLoanExecuted(address indexed receiver, address indexed token, uint256 amount, uint256 fee);
event FeeUpdated(uint256 newFee);
constructor() Ownable(msg.sender) {}
/**
* @notice 计算闪电贷费用
*/
function getFee(uint256 amount) public view returns (uint256) {
return (amount * flashLoanFee) / FEE_PRECISION;
}
/**
* @dev 执行闪电贷
*/
function flashLoan(
address receiver,
address token,
uint256 amount,
bytes calldata params
) external nonReentrant {
IERC20 asset = IERC20(token);
uint256 balanceBefore = asset.balanceOf(address(this));
if (balanceBefore < amount) revert InsufficientLiquidity();
uint256 fee = getFee(amount);
// 使用 SafeERC20 处理转账
asset.safeTransfer(receiver, amount);
// 执行回调
if (!IFlashLoanReceiver(receiver).executeOperation(
token,
amount,
fee,
msg.sender,
params
)) revert InvalidReceiver();
// 验证还款:使用 safeTransferFrom 的逻辑或直接余额检查
// 建议要求接收者将资金发回,而不是让 FlashLoan 合约主动拉取,除非已授权
uint256 balanceAfter = asset.balanceOf(address(this));
if (balanceAfter < balanceBefore + fee) revert RepaymentFailed();
emit FlashLoanExecuted(receiver, token, amount, fee);
}
function updateFee(uint256 _newFee) external onlyOwner {
require(_newFee <= 500, "Fee too high"); // 最高不超过 5%
flashLoanFee = _newFee;
emit FeeUpdated(_newFee);
}
}
### 3.接收者合约
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract FlashLoanReceiver { using SafeERC20 for IERC20;
address public immutable pool;
error OnlyPoolAllowed();
error ArbitrageFailed();
constructor(address _pool) {
pool = _pool;
}
/**
* @dev 闪电贷回调
*/
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address /* initiator */,
bytes calldata /*params*/
) external returns (bool) {
// 1. 安全检查:只允许受信任的闪电贷池调用
if (msg.sender != pool) revert OnlyPoolAllowed();
// 2. 在此处编写你的套利或清算逻辑
// 例如:在 DEX A 买入,在 DEX B 卖出
// _performArbitrage(asset, amount, params);
// 3. 确保当前合约余额足以支付本金 + 手续费
uint256 amountToRepay = amount + premium;
uint256 currentBalance = IERC20(asset).balanceOf(address(this));
if (currentBalance < amountToRepay) revert ArbitrageFailed();
// 4. 还款给闪电贷池
IERC20(asset).safeTransfer(pool, amountToRepay);
return true;
}
// 允许提取利润
function withdraw(address token) external {
// 应当添加仅限管理员调用的 modifier
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransfer(msg.sender, balance);
}
}
### 4.恶意的接收者合约
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20;
/**
@dev 用于测试闪电贷失败场景的恶意合约 */ contract MaliciousNonPayer { /**
// 返回 true 诱导闪电贷合约继续执行,从而触发其最后的余额安全检查 return true; } }
## 合约测试
import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { parseEther, formatEther } from 'viem'; import { network ,artifacts} from "hardhat";
describe("FlashLoan 核心功能测试 (2026版)", async function () { // 1. 部署环境准备 async function deployFixture() { const { viem } = await network.connect(); const publicClient = await viem.getPublicClient(); const [owner, user] = await viem.getWalletClients();
// 部署 Token 合约 (参数:owner, initialOwner)
const tokenContract = await viem.deployContract("BoykaYuriToken", [owner.account.address, owner.account.address]);
console.log("Token 部署成功,地址:", tokenContract.address);
//部署FlashLoan合约
const flashLoanContract = await viem.deployContract("FlashLoan", []);
console.log("FlashLoan 部署成功,地址:", flashLoanContract.address);
//部署FlashLoanReceiver合约
const flashLoanReceiverContract = await viem.deployContract("FlashLoanReceiver", [flashLoanContract.address]);
console.log("FlashLoanReceiver 部署成功,地址:", flashLoanReceiverContract.address);
//部署MaliciousNonPayer合约
const maliciousNonPayerContract = await viem.deployContract("MaliciousNonPayer");
console.log("MaliciousNonPayer 部署成功,地址:", maliciousNonPayerContract.address);
// // 2. 初始化 FlashLoan 合约转1000 MTK
// await tokenContract.write.transfer([
// flashLoanContract.address,
// parseEther("1000")
// ]);
// //查看FlashLoan合约余额
// const flashLoanBalance = await tokenContract.read.balanceOf([flashLoanContract.address]);
// console.log("FlashLoan合约余额:", formatEther(flashLoanBalance) + " MTK");
return { tokenContract, flashLoanContract, maliciousNonPayerContract, owner, user, publicClient, flashLoanReceiverContract };
}
it("闪电贷执行成功", async function () {
const { tokenContract, flashLoanContract, flashLoanReceiverContract, owner, user, publicClient } = await deployFixture();
// --- Step 1: owner查看账户余额 ---
const initialBalance = await tokenContract.read.balanceOf([owner.account.address]);
console.log("用户账户余额:", formatEther(initialBalance) + " MTK");
// 2. 初始化 FlashLoan 合约转1000 MTK
await tokenContract.write.transfer([
flashLoanContract.address,
parseEther("1000")
]);
//查看FlashLoan合约余额
const flashLoanBalancei = await tokenContract.read.balanceOf([flashLoanContract.address]);
console.log("FlashLoan合约余额:", formatEther(flashLoanBalancei) + " MTK");
//
const loanAmount = parseEther("100");
const fee = (loanAmount * BigInt(5)) / BigInt(10000); // 0.05% fee
// 给接收者转一些代币用于支付费用
await tokenContract.write.transfer([
await flashLoanReceiverContract.address,
fee
]);
// 执行闪电贷
await
flashLoanContract.write.flashLoan([
await flashLoanReceiverContract.address,
await tokenContract.address,
loanAmount,
"0x"
])
// 验证 FlashLoan 合约的余额
const flashLoanBalance = await tokenContract.read.balanceOf([flashLoanContract.address]);
console.log("FlashLoan合约余额:", formatEther(flashLoanBalance) + " MTK");
console.log("预期FlashLoan合约余额:", formatEther(parseEther("1000") + fee) + " MTK");
});
it("贷款金额为0 失败", async function () {
const { tokenContract, flashLoanContract, flashLoanReceiverContract, owner, user, publicClient } = await deployFixture();
const flashLoanTx = await flashLoanContract.write.flashLoan([
await flashLoanReceiverContract.address,
await tokenContract.address,
0n,
"0x"
])
console.log("贷款金额为0 失败:", flashLoanTx);
// .catch((error) => {
// console.log("贷款金额为0 失败:", error.message);
// });
});
it("token不足 失败", async function () {
const { tokenContract, flashLoanContract, flashLoanReceiverContract, owner, user, publicClient } = await deployFixture();
// 2. 初始化 FlashLoan 合约转1000 MTK
await tokenContract.write.transfer([
flashLoanContract.address,
parseEther("1000")
]);
//查看FlashLoan合约余额
const flashLoanBalancei = await tokenContract.read.balanceOf([flashLoanContract.address]);
console.log("FlashLoan合约余额:", formatEther(flashLoanBalancei) + " MTK");
const hugeAmount = parseEther("10000");
await flashLoanContract.write.flashLoan([
await flashLoanReceiverContract.address,
await tokenContract.address,
hugeAmount,
"0x"
]).catch((error) => {
console.log("token不足 失败:", error.message);
});
});
it("贷款未偿还 失败", async function () {
const { viem } = await network.connect();
const { tokenContract, flashLoanContract, maliciousNonPayerContract, owner, user, publicClient } = await deployFixture();
// 部署一个恶意的接收者合约,它不会还款
// const maliciousReceiver = await viem.deployContract("MaliciousNonPayer");
// const maliciousReceiverAddress = maliciousReceiver.address;
// 2. 确保闪电贷池子里有足够的钱借出去
await tokenContract.write.transfer([flashLoanContract.address, parseEther("1000")]);
// 不给恶意接收者转入费用代币
await flashLoanContract.write.flashLoan([
await maliciousNonPayerContract.address,
await tokenContract.address,
parseEther("100"),
"0x"
]).catch((error) => {
console.log("贷款未偿还 失败:", error.message);
});
});
});
## 合约部署
import { network, artifacts } from "hardhat"; import { parseEther, parseEventLogs, getAddress } from "viem";
async function main() {
console.log(--- 开始在网络: ${network.name} 部署 ---);
// 1. 初始化客户端 const { viem } = await network.connect(); const [deployer] = await viem.getWalletClients(); const publicClient = await viem.getPublicClient(); const deployerAddress = deployer.account.address;
// 2. 部署 Token 合约
console.log("正在部署 Token...");
const TokenArtifact = await artifacts.readArtifact("BoykaYuriToken");
const tokenHash = await deployer.deployContract({
abi: TokenArtifact.abi,
bytecode: TokenArtifact.bytecode as 0x${string},
args: [deployerAddress, deployerAddress],
});
const TokenReceipt = await publicClient.waitForTransactionReceipt({ hash: tokenHash });
console.log("Token 部署成功,地址:", TokenReceipt.contractAddress!);
// 3. 部署 FlashLoan 合约
console.log("正在部署 FlashLoan...");
const FlashLoanArtifact = await artifacts.readArtifact("FlashLoan");
const flashLoanHash = await deployer.deployContract({
abi: FlashLoanArtifact.abi,
bytecode: FlashLoanArtifact.bytecode as 0x${string},
args: [],
});
const FlashLoanReceipt = await publicClient.waitForTransactionReceipt({ hash: flashLoanHash });
console.log("FlashLoan 部署成功,地址:", FlashLoanReceipt.contractAddress!);
//接受合约
console.log("正在部署 FlashLoanReceiver...");
const FlashLoanReceiverArtifact = await artifacts.readArtifact("FlashLoanReceiver");
const flashLoanReceiverHash = await deployer.deployContract({
abi: FlashLoanReceiverArtifact.abi,
bytecode: FlashLoanReceiverArtifact.bytecode as 0x${string},
args: [FlashLoanReceipt.contractAddress!],
});
const FlashLoanReceiverReceipt = await publicClient.waitForTransactionReceipt({ hash: flashLoanReceiverHash });
console.log("FlashLoanReceiver 部署成功,地址:", FlashLoanReceiverReceipt.contractAddress!);
// 4. 部署 MaliciousNonPayer 合约
console.log("正在部署 MaliciousNonPayer...");
const MaliciousNonPayerArtifact = await artifacts.readArtifact("MaliciousNonPayer");
const maliciousNonPayerHash = await deployer.deployContract({
abi: MaliciousNonPayerArtifact.abi,
bytecode: MaliciousNonPayerArtifact.bytecode as 0x${string},
args: [],
});
const MaliciousNonPayerReceipt = await publicClient.waitForTransactionReceipt({ hash: maliciousNonPayerHash });
console.log("MaliciousNonPayer 部署成功,地址:", MaliciousNonPayerReceipt.contractAddress!);
}
main().catch((error) => { console.error(error); process.exit(1); });
# 总结
至此,本文完成了 DeFi 闪电贷从理论到落地的全链路梳理与实现:明确其无抵押、原子化核心属性,厘清功能、痛点与行业应用,依托 Hardhat V3 与 OpenZeppelin 实现核心合约开发,并配套测试与部署流程,形成完整实践闭环。
闪电贷的价值实现依赖合约安全与逻辑严谨,本文提供的合约设计及全场景测试思路可提供实用参考。未来随着 DeFi 演进,其应用场景将进一步拓展,合约安全优化与多链适配仍是核心探索方向。 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!