【免费下载链接】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() 传入的附加数据 |
逻辑分三步:
偿还路径的底层实现在基类 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 地址如下(源码中硬编码,务必与所选网络匹配):
| ropsten | 0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728 |
| kovan | 0x506B0B2CF20FAA8f38a4E2B524EE43e1f4458Cc5 |
| matic(Mumbai 测试网) | 0x87A5b1cD19fC93dfeb177CCEc3686a48c53D65Ec |
随后脚本依次完成:
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 必须遵守的安全约束
七、总结
通过 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),仅供参考





