欢迎光临
我们一直在努力

Dapp-Learning 实战:基于 Aave 的闪电贷(Flash Loan)智能合约开发、部署与验证全指南

  • 示例工程
  • 区块链

【免费下载链接】Dapp-Learning

Dapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.

项目地址:
https://gitcode.com/gh_mirrors/da/Dapp-Learning

点击查看 免费下载

导读

本文以 Dapp-Learning 仓库 basic/20-flash-loan/aave 子目录为实战载体,系统拆解 Aave 闪电贷(Flash Loan)智能合约的完整实现:从 Flashloan.sol 的继承体系、flashloan() 发起函数,到 executeOperation() 回调与"本金 + 手续费"偿还机制,再到基于 Hardhat 的环境配置、合约部署与链上验证。读完本文,你可以独立部署一个可用的 Aave 闪电贷合约,并在此基础上扩展套利、清算等业务逻辑。

一、闪电贷:为什么可以"无抵押"借贷

闪电贷的核心思想在仓库父目录文档 basic/20-flash-loan/README-CN.md 中有非常直观的阐述:你无需任何抵押物,只要保证在一笔交易(transaction)内完成还款,就可以贷出任意资产。

其可行性建立在以太坊交易的原子性之上。一笔交易本质上是"调用某个智能合约里的某个函数",而一个函数内部可以完成借款、转移、套利、还款等一系列操作。闪电贷要求所有操作都在同一笔交易内完成:如果交易结束时未能还款,整笔交易就会失败并全部回滚,"就像所有事情都没有发生过一样";反之,如果成功还款,合约传出"执行成功",整个网络的状态完成更改。因此,闪点贷合约的资金并不是真的"借出",而是在同一笔交易内的短暂周转——这就是它无需抵押的原因。

仓库中该模块一共提供了四个闪电贷实战示例,分别位于 aave、dydx、uniswapv2、uniswapv3 子目录,本文聚焦 Aave(v1 闪电贷)的实现。

二、实战项目结构一览

先看清 aave 目录的文件布局,理解每个文件的职责:

basic/20-flash-loan/aave/
├── contracts/
│ ├── Flashloan.sol # 主合约:发起闪电贷 + 回调还款
│ ├── aave/
│ │ ├── FlashLoanReceiverBase.sol # Aave 提供的抽象基类(接收与偿还逻辑)
│ │ ├── IFlashLoanReceiver.sol # executeOperation 回调接口
│ │ ├── ILendingPool.sol # Aave LendingPool 接口
│ │ └── ILendingPoolAddressesProvider.sol # 地址提供者接口
│ └── utils/
│ └── Withdrawable.sol # 合约余额提现工具(Ownable)
├── scripts/
│ └── deploy_aave_flashloan.js # 部署 + 自动发起闪电贷的脚本
├── .env.example # 环境变量模板
├── hardhat.config.js # Hardhat 网络与编译器配置
└── package.json # 依赖声明

其中 Flashloan.sol 是用户侧的核心合约,其余均为其依赖的接口与基类。整个调用关系是:用户 → flashloan() → LendingPool.flashLoan() → 资金转入本合约 → LendingPool 回调 executeOperation() → 业务逻辑 → 偿还本金+手续费 → 交易成功。

三、闪电贷合约逻辑逐层拆解

3.1 Flashloan.sol:继承体系与构造函数

Flashloan.sol 的合约主体非常精简:

pragma solidity ^0.8.0;

import "./aave/FlashLoanReceiverBase.sol";
import "./aave/ILendingPoolAddressesProvider.sol";
import "./aave/ILendingPool.sol";
import '@openzeppelin/contracts/utils/math/SafeMath.sol';

contract Flashloan is FlashLoanReceiverBase {

// address public kovanWETHAddr = 0xd0A1E359811322d97991E03f863a0C30C2cF029C;
// address public kovanDaiAddr = 0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD;
// address public kovanAaveLendingPoolAddr = 0x506B0B2CF20FAA8f38a4E2B524EE43e1f4458Cc5;
using SafeMath for uint256;
event TotalDebt( uint256 totalDebt);

constructor(address _addressProvider) FlashLoanReceiverBase(_addressProvider) public {}
…
}

要点如下:

  • 合约继承自 FlashLoanReceiverBase(见 FlashLoanReceiverBase.sol),这是一个抽象合约,封装了"判断余额、向 LendingPool 还款、区分 ETH 与 ERC20 转账"等常用方法,避免每个使用者重复实现。
  • 构造函数接收 Aave 的 LendingPoolAddressesProvider(地址提供者) 地址,并向上传递给基类保存。这个地址是一个注册中心,通过它才能动态获取当前生效的 LendingPool 合约地址。
  • 合约引入 OpenZeppelin 的 SafeMath,在 ^0.8.0 版本下配合 uint totalDebt = _amount.add(_fee); 使用(0.8 起内置溢出检查,SafeMath 在此更多是兼容旧写法的示例)。
  • 代码注释中保留了 kovan 测试网的参考地址:WETH 为 0xd0A1E359811322d97991E03f863a0C30C2cF029C、DAI 为 0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD、LendingPool 为 0x506B0B2CF20FAA8f38a4E2B524EE43e1f4458Cc5,可供测试时参考。

3.2 flashloan():发起闪电贷

Flashloan.sol 中的 flashloan() 是发起借款的入口:

/**
Flash loan 1000000000000000000 wei (1 ether) worth of `_asset`
*/
function flashloan(address _asset) public {
bytes memory data = "";
uint amount = 1 ether;

ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool());
lendingPool.flashLoan(address(this), _asset, amount, data);
}

逐项说明:

  • _asset:要借的资产地址。传 ETH 的 Aave 约定伪地址 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 即可借 ETH;传 DAI 合约地址则借 DAI。
  • uint amount = 1 ether;:这里的 1 ether 在 Solidity 中等于 10^18 wei,它只是"数量单位"而非固定的 ETH 语义。若 _asset 是 ETH 地址,你借到 1 ETH;若 _asset 是 DAI 地址,你借到 1 DAI(DAI 同样是 18 位小数)。
  • bytes memory data = "";:附加参数,会原样透传给 executeOperation 的 _params,可用来携带业务指令。
  • 动态获取 LendingPool:通过 addressesProvider.getLendingPool() 拿到当前生效的 LendingPool 合约地址(见 ILendingPoolAddressesProvider.sol),而不是写死地址,这是 Aave 架构解耦的关键设计。
  • 发起借款:调用 lendingPool.flashLoan(address(this), _asset, amount, data),参数分别为"接收回调的合约地址(即本合约)"、"储备资产"、"金额"、"附加数据"。

值得注意:本仓库版本中 flashloan() 是 public 且没有 onlyOwner 限制(README 中也特意注明"去掉 onlyOwner,任何人都可调用 flashloan"),即任何账户都可以调用它发起一笔借款。

3.3 executeOperation():回调与偿还

闪电贷的资金转入本合约后,LendingPool 会自动回调 executeOperation():

function executeOperation(
address _reserve,
uint256 _amount,
uint256 _fee,
bytes calldata _params
)
external override
{
require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance, was the flashLoan successful?");

//
// Your logic goes here.
// !! Ensure that *this contract* has enough of `_reserve` funds to payback the `_fee` !!
//

uint totalDebt = _amount.add(_fee);
emit TotalDebt(totalDebt);
transferFundsBackToPoolInternal(_reserve, totalDebt);
}

四个回调参数由 LendingPool 自动传入,含义分别是:

参数含义
_reserve 本次借款的资产地址(如 WETH / DAI / ETH 伪地址)
_amount 实际借到的金额
_fee Aave 收取的闪电贷手续费
_params 发起方通过 flashLoan() 传入的附加数据

逻辑分三步:

  • 校验到账:require(_amount <= getBalanceInternal(address(this), _reserve), …) 通过 getBalanceInternal 检查本合约持有的 _reserve 余额是否不小于借款金额,确认闪电贷确实成功打款。getBalanceInternal 在基类中区分了 ETH(返回 address(this).balance)和 ERC20(调用 balanceOf)两种情况,见 FlashLoanReceiverBase.sol。
  • 插入业务逻辑:注释中 // Your logic goes here. 就是套利、清算等自定义操作的插入点——此刻本合约已持有全部可用资金。注意注释的警告:必须保证本合约在回调结束前有足够资金偿还本金与手续费,否则整笔交易回滚。
  • 计算并偿还:uint totalDebt = _amount.add(_fee); 计算总债务 = 借款本金 + 手续费。Aave 闪电贷手续费为借款金额的 0.09%(即 9 个基点),本仓库代码通过 emit TotalDebt(totalDebt) 抛出事件便于链上观察,随后调用 transferFundsBackToPoolInternal(_reserve, totalDebt) 将本金与手续费一并归还。
  • 偿还路径的底层实现在基类 FlashLoanReceiverBase.sol 中:先通过 addressesProvider.getLendingPoolCore() 拿到 LendingPoolCore 地址,再调用 transferInternal 完成转账——若 _reserve 是 ETH 伪地址则用 call{value: _amount} 转账,否则用 SafeERC20 的 safeTransfer 转账 ERC20。

    3.4 基类与接口:理解 Aave 的合约约定

    • IFlashLoanReceiver.sol:定义了闪电贷接收方必须实现的回调接口 executeOperation(address, uint256, uint256, bytes),Flashloan 合约正是通过 external override 实现该接口才被 LendingPool 认可。
    • ILendingPool.sol:完整罗列了 LendingPool 的能力,除了 flashLoan 外还包括 deposit、borrow、repay、liquidationCall、getReserveData、getUserAccountData 等,你可以基于同一接口在 executeOperation 中组合存款、借款、清算等更多玩法。
    • Withdrawable.sol:合约继承了 Ownable,提供 withdraw(address) 方法,允许 owner 将合约中意外滞留或多余的资金(包括 ETH 与任意 ERC20)提走,避免手续费预算等资金被锁死。
    • FlashLoanReceiverBase.sol 中定义了 address constant ethAddress = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;,这是 Aave 表达"原生 ETH"的约定地址;合约还声明了 receive() external payable {},保证能接收用于偿还的 ETH。

    四、环境准备:依赖安装与密钥配置

    进入 basic/20-flash-loan/aave 目录后,按以下步骤准备运行环境:

    1. 安装依赖

    yarn

    依赖清单见 package.json,核心包括 hardhat、@nomiclabs/hardhat-ethers、@nomiclabs/hardhat-waffle、ethers、@openzeppelin/contracts 和 dotenv。

    2. 配置环境变量

    cp .env.example .env
    # 在 .env 中配置 INFURA_ID、PRIVATE_KEY

    .env.example 的模板为:

    INFURA_ID = ""
    PRIVATE_KEY = "1111111111111111111111111111111111111111111111111111111111111111"

    • INFURA_ID:Infura 项目 ID,用于连接 Rinkeby、Kovan、Ropsten、Mainnet 等以太坊网络节点(见 hardhat.config.js,URL 统一拼为 https://<network>.infura.io/v3/<INFURA_ID>)。
    • PRIVATE_KEY:部署账户的私钥(64 位十六进制)。示例中的全 1 占位私钥必须替换为你自己的私钥,且该账户需持有测试网 ETH 以支付 Gas 与手续费。
    • .env 已被 .gitignore 忽略,不会被提交到仓库,但务必妥善保管私钥,切勿泄露。

    3. 理解网络配置

    hardhat.config.js 中同时配置了 Solidity 多版本编译器(0.8.1、0.6.12、0.5.4、0.4.18,用于兼容不同依赖)以及 localhost、rinkeby、kovan、mainnet、ropsten、matic 六个网络。README 示例选用 kovan 测试网。

    五、部署与发起闪电贷实战

    5.1 部署脚本做了什么

    deploy_aave_flashloan.js 并不只是部署,而是把部署 → 充值手续费 → 发起闪电贷三条步骤封装在一个脚本里:

    // 1. 根据所选网络确定 LendingPoolAddressesProvider 地址
    switch (network) {
    case 'ropsten':
    lendingPoolProviderAddr = '0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728';
    break;
    case 'kovan':
    lendingPoolProviderAddr = '0x506B0B2CF20FAA8f38a4E2B524EE43e1f4458Cc5';
    break;
    case 'matic':
    lendingPoolProviderAddr = '0x87A5b1cD19fC93dfeb177CCEc3686a48c53D65Ec';
    break;
    default:
    throw console.error(`Are you deploying to the correct network? (network selected: ${network})`);
    }

    各网络对应的 LendingPoolAddressesProvider 地址如下(源码中硬编码,务必与所选网络匹配):

    网络LendingPoolAddressesProvider 地址
    ropsten 0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728
    kovan 0x506B0B2CF20FAA8f38a4E2B524EE43e1f4458Cc5
    matic(Mumbai 测试网) 0x87A5b1cD19fC93dfeb177CCEc3686a48c53D65Ec

    随后脚本依次完成:

  • 部署合约:用 ethers.getContractFactory('Flashloan') 创建工厂并以 lendingPoolProviderAddr 为构造参数部署,输出合约地址;
  • 转入手续费预算:部署账户向合约地址转账 0.02 ETH(ethers.utils.parseEther('0.02')),用于覆盖闪电贷手续费——1 ETH 借款按 0.09% 计算手续费为 0.0009 ETH,0.02 ETH 的预算绰绰有余;
  • 发起闪电贷:
  • let asset = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
    // do flashloan
    let res = await flashloan.flashloan(asset, { gasLimit: 250000 });
    let res1 = await res.wait();
    console.log(`Flashloan successfully, please check tx hash ${res.hash} for more details `);

    这里 asset 使用的正是 Aave 的原生 ETH 伪地址,gasLimit: 250000 为借款交易预留足够的 Gas。脚本最后打印 Flashloan successfully, please check tx hash …,返回的 tx hash 即可用于后续链上验证。

    5.2 按 README 的步骤操作

    README 给出的完整操作流程如下:

    # 部署合约(README 示例使用 kovan 测试网)
    npx hardhat run scripts/deploy_aave_flashloan.js –network kovan

    # 发起闪电贷(Hardhat 测试入口)
    npx hardhat test –network kovan
    # 交易完成后,根据返回的 tx hash 检查交易细节

    需要说明的是:本仓库的 aave 子目录并未附带 test 用例文件,实际的"部署 + 发起闪电贷"完整流程已经封装在上文的部署脚本中,运行部署命令即可在输出中拿到交易哈希;README 中的 npx hardhat test –network kovan 是 Hardhat 测试套件的标准入口(对应 package.json 中的 "test": "npx hardhat test"),若目录中没有测试用例则不会真正触发借款。

    5.3 交易验证

    交易完成后,根据脚本输出的 tx hash 在区块浏览器中检查交易细节,重点确认:

    • executeOperation 回调是否成功执行(可通过 TotalDebt 事件观察本合约计算出的"本金 + 手续费"总额);
    • 合约是否在同一笔交易内完成了还款,从而保证整笔交易成功落账。

    六、进阶:扩展业务逻辑与安全注意

    6.1 套利/清算逻辑的插入位置

    executeOperation() 中 // Your logic goes here. 注释处就是业务扩展点。典型玩法包括:

    • 套利:回调中利用借来的资金在去中心化交易所(如 Uniswap)进行低买高卖,获利后再还款,利润归合约所有;
    • 清算:调用 ILendingPool.liquidationCall(…) 帮助他人清算头寸并赚取清算奖励(该函数已在 ILendingPool.sol 接口中声明);
    • 组合操作:利用 deposit、borrow 等接口在同一回调内完成多步 DeFi 操作。

    6.2 必须遵守的安全约束

  • 还款能力是硬前提:回调结束前,本合约必须持有 _amount + _fee 的资金;否则整个交易回滚,借款像从未发生一样。注释中的 !! Ensure that *this contract* has enough of _reserve funds to payback the _fee !! 正是这一约束的强调。
  • 手续费预算:发起前需为合约充值足够的 ETH/ERC20 以覆盖 0.09% 的手续费,否则回调中偿还失败导致交易失败。
  • 资金兜底:多余的充值资金可通过继承自 Withdrawable.sol 的 withdraw() 方法由 owner 提走。
  • 网络适配:仓库示例基于 kovan、ropsten 等早期测试网(这些测试网已陆续停用),在新环境实操时应参照 Aave 官方最新的部署信息替换网络与 LendingPoolAddressesProvider 地址,合约核心逻辑无需改动;若使用 Aave V3,可参考仓库中同模块的 aavev3 示例。
  • 七、总结

    通过 basic/20-flash-loan/aave 这一完整示例,我们走通了 Aave 闪电贷的整条链路:理解"一笔交易内借款-操作-还款"的原子性原理;掌握 Flashloan.sol 通过继承 FlashLoanReceiverBase 获得的接收与偿还能力;学会用 flashloan() 发起借款、在 executeOperation() 中编写业务逻辑并按"本金 + 0.09% 手续费"偿还;最后借助 Hardhat 完成从环境配置、合约部署到链上交易验证的全流程。

    在此基础上,你可以直接复用该模板:把 // Your logic goes here. 替换为你的套利或清算策略,结合 dydx、uniswapv2、uniswapv3 等仓库内其他闪电贷实现横向对比,深入理解不同协议在回调接口、手续费与资金流转上的设计差异,从而在自己的 DApp 中安全地驾驭闪电贷这一强大原语。

    赞

    分享

    • 示例工程
    • 区块链

    【免费下载链接】Dapp-Learning

    Dapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.

    项目地址:
    https://gitcode.com/gh_mirrors/da/Dapp-Learning

    点击查看 免费下载

    上一篇:
    Windows右键菜单终极优化指南:ContextMenuManager深度定制全解析

    下一篇:
    nanobot 贡献指南:从开发环境搭建、代码规范到发布打包契约的完整工程手册

    创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

    赞(0)
    未经允许不得转载:171主机测评 » Dapp-Learning 实战:基于 Aave 的闪电贷(Flash Loan)智能合约开发、部署与验证全指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址