# 闪电贷学习和编写 **Published by:** [想住大房子的java程序员](https://paragraph.com/@java-3/) **Published on:** 2022-11-01 **URL:** https://paragraph.com/@java-3/in5NdNN5IICqTfyKm8xB ## Content 1.什么是闪电贷?关于闪电贷的概念,网络上有很多。一句话总结就是一个区块内借款和还款同时完成。 听起来第一个想法就是:有个毛用? 对,普通情况下就是没卵用。但是没用也要去学习他。为了学习它,总得找个理由让自己打个鸡血。 emmm,想到了! 那就是发起一笔闪电贷,拿来买大房子。这样子我就可以拥有它1-12秒钟!2.和谁贷款?提供闪电贷的很多。有aave,uni,keeperdao等等。。 其他的相对复杂,就先拿keeperDao来举例吧。因为它最简单,也是yueying大佬最先开始写的。 它的合约地址是3.怎么借?知道合约地址了可以查看代码,还有去官网看说明。 最主要的函数/// @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); } 好了。知道了主要的函数之后,就可以想一想应该怎么做了。4.开始写发起闪电贷的代码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个核桃。 ## Publication Information - [想住大房子的java程序员](https://paragraph.com/@java-3/): Publication homepage - [All Posts](https://paragraph.com/@java-3/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@java-3): Subscribe to updates