关于闪电贷的概念,网络上有很多。一句话总结就是一个区块内借款和还款同时完成。
听起来第一个想法就是:有个毛用?
对,普通情况下就是没卵用。但是没用也要去学习他。为了学习它,总得找个理由让自己打个鸡血。
emmm,想到了!
那就是发起一笔闪电贷,拿来买大房子。这样子我就可以拥有它1-12秒钟!
提供闪电贷的很多。有aave,uni,keeperdao等等。。
其他的相对复杂,就先拿keeperDao来举例吧。因为它最简单,也是yueying大佬最先开始写的。
它的合约地址是
知道合约地址了可以查看代码,还有去官网看说明。
最主要的函数
/// @notice borrow assets from this LP, and return them within the same transaction.
///
/// @param _token The address of the token contract.
/// @param _amount The amont of token.
/// @param _data The implementation specific data for the Borrower.
function borrow(address _token, uint256 _amount, bytes calldata _data) external nonReentrant whenNotPaused {
require(address(kTokens[_token]) != address(0x0), "Token is not registered");
uint256 initialBalance = borrowableBalance(_token);
_transferOut(_msgSender(), _token, _amount);
borrower.lend(_msgSender(), _data);
uint256 finalBalance = borrowableBalance(_token);
require(finalBalance >= initialBalance, "Borrower failed to return the borrowed funds");
uint256 fee = finalBalance - initialBalance;
uint256 poolFee = calculateFee(poolFeeInBips, fee);
emit Borrowed(_msgSender(), _token, _amount, fee);
_transferOut(feePool, _token, poolFee);
}
好了。知道了主要的函数之后,就可以想一想应该怎么做了。
PS:再写之前,先去上个洗手间,洗洗手,泡杯咖啡。思路清晰之后再去做。写这篇文章的时候,我是在公司写的,有效利用摸鱼时间。毕竟我是十年java程序员,工作效率极高之下,时间多一点是很正常的。
先创建ExInterface.sol文件,里面主要编写引用的外部合约。文件内容如下:
pragma solidity ^0.8.17;
//ERC20标准token的接口
interface IERC20 {
//总供应量(totalSupply)
function totalSupply() external view returns (uint256);
//余额(balanceOf)
function balanceOf(address account) external view returns (uint256);
//转账(transfer)
function transfer(address recipient, uint256 amount) external returns (bool);
//授权(approve)
function approve(address spender, uint256 amount) external returns (bool);
//请求转账(transferFrom)
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function decimals() external view returns (uint8);
function allowance(address owner, address spender) external view returns (uint256);
}
//将以太坊原生代币ETH与ERC20token互相转换的合约
//合约地址 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
interface IWETH {
//将ETH转换为WETH
function deposit() external payable;
//将ETH转换为WETH
function withdraw(uint wad) external;
}
//KeeperDao的LiquidityPool合约
//合约地址0x4F868C1aa37fCf307ab38D215382e88FCA6275E2
interface ILiquidity {
//_token 所借的代币名称;_amount 借多少金额;calldata 回调函数
function borrow(address _token, uint256 _amount, bytes calldata _data) external;
}
相信代码解释已经足够清晰。这里就不再详细解释。最主要的是ILiquidity 接口的borrow()一眼就能看懂了吧。
至于IERC20 和IWETH 为什么要这么写,也暂时不用去管它。
然后,创建我们的主要合约文件FlashLoan.sol
PS:前辈们说的,只要你写合约,第一件事就是先写上代币转出的功能。
所以,求生欲望极强的我,马上写上(复制)以下几个函数
//把合约里面的ETH转到钱包
function turnOutETH(uint256 amount) public onlyOwner {
payable(owner).transfer(amount);
}
//把合约里面的钱转到钱包
function turnOutToken(address token, uint256 amount) public onlyOwner {
IERC20(token).transfer(owner, amount);
}
// WETH转换为ETH
function WETHToETH(uint256 amount) public onlyOwner {
IWETH(WETH).withdraw(amount);
}
// ETH转换为WETH
function ETHtoWETH(uint256 amount) public onlyOwner {
IWETH(WETH).deposit{value : amount}();
}
然后创建两个函数,一个是发起闪电贷的主函数,一个是接收闪电贷的回调函数。
//发起一个eth的闪电贷。这里先不自定义传入token类别了
function doFlashEth(uint256 amount){
address liquidityPool = 0x4F868C1aa37fCf307ab38D215382e88FCA6275E2;
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
ILiquidity(liquidityPool).borrow(weth, amount,
abi.encodeWithSelector(this.receiveBuyBigHouse.selector, abi.encode(amount)));
}
// callback ,收到钱后可以拿来买一个大房子。
function receiveBuyBigHouse(bytes memory data) public {
require(msg.sender == borrowerProxy, "bu shi BorrorwerProxy,paochu yichang.");
//buy big house--->.
address liquidityPool = 0x4F868C1aa37fCf307ab38D215382e88FCA6275E2;
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
uint256 amount=abi.decode(data, (uint256));
IERC20(weth).transfer(liquidityPool, amount);
}
在发起闪电贷的函数中doFlashEth,由于我比较懒,所以直接把地址都写在函数里面了。后面我们再想办法进行扩展。嗯,那句话怎么说的?先实现,右面再优化~(我都辞职了,你还叫我优化?)
abi.encodeWithSelector 这个后面再具体说明,这里先这么写。就是告诉它,携带参数回调我们的购买大房子函数。
然后在receiveBuyBigHouse购买大房子里面,我们会看到屎山已经形成,不得不去稍微优化一下,否则代码又臭又长。
当然,不要忘记把购买大房子的代码删了。
最终,我们的代码如下
address liquidityPool = 0x4F868C1aa37fCf307ab38D215382e88FCA6275E2;
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
//发起一个eth的闪电贷
function doFlashEth(uint256 amount){
ILiquidity(liquidityPool).borrow(weth, amount,
abi.encodeWithSelector(this.receiveBuyBigHouse.selector, abi.encode(amount)));
}
// callback ,收到钱后可以拿来买一个大房子。
function receiveBuyBigHouse(bytes memory data) public {
require(msg.sender == borrowerProxy, "bu shi BorrorwerProxy,paochu yichang.");
//购买大房子
uint256 amount = abi.decode(data, (uint256));
IERC20(weth).transfer(liquidityPool, amount);
}
千万记得要先购买房子,再还钱,否则你白忙一场。也记得要还钱。否则整个闪电贷就不会成功。也就是这一句代码
require(finalBalance >= initialBalance, "Borrower failed to return the borrowed funds");
大家看到我对大房子的执念得有多深。。。
到这里,接下来便可以进行合约的部署和测试。
我把自己写的代码注释放上。有助于理解。
/// @notice borrow assets from this LP, and return them within the same transaction.
///
/// @param _token The address of the token contract.
/// @param _amount The amont of token.
/// @param _data The implementation specific data for the Borrower.
function borrow(address _token, uint256 _amount, bytes calldata _data) external nonReentrant whenNotPaused {
//检测是否支持该代币
require(address(kTokens[_token]) != address(0x0), "Token is not registered");
//获得代币里面的余额
uint256 initialBalance = borrowableBalance(_token);
//执行转账。如果不成功,会出现异常并回退操作。
_transferOut(_msgSender(), _token, _amount);
//执行借款检查和回调。
borrower.lend(_msgSender(), _data);
//再次获得代币里面的余额
uint256 finalBalance = borrowableBalance(_token);
//如果最终余额大于等于初始余额,证明已经归还借款。否则抛出异常。
require(finalBalance >= initialBalance, "Borrower failed to return the borrowed funds");
//比较两个数之间的差值--费用
uint256 fee = finalBalance - initialBalance;
//计算手续费
uint256 poolFee = calculateFee(poolFeeInBips, fee);
//通知事件
emit Borrowed(_msgSender(), _token, _amount, fee);
//执行转账 给费用池
_transferOut(feePool, _token, poolFee);
}
contract BorrowerProxy {
address liquidityPool;
constructor() {
liquidityPool = msg.sender;
}
function lend(address _caller, bytes calldata _data) external payable {
//检测是谁调用了合约。如果不是borrowerProxy,则抛出异常。
require(msg.sender == borrowerProxy, "BorrowerProxy: Caller is not the borrowerProxy pool");
//调用我们的的回调函数
(bool success,) = _caller.call{value : msg.value}(_data);
require(success, "BorrowerProxy: Borrower contract reverted during execution");
}
}
好了。下一节,我们将使用truffle进行合约测试。
PS:最近脑袋有点晕,如果文章帮助到你,可以打赏一点给我买8个核桃。

