本文介绍了Uniswap的Flash Swap概念及其实现方法,详细讲解了Flash Loan的工作原理,包括如何利用Flash Loan进行套利交易,并提供了代码示例和测试脚本,让读者能够自主实现Flash Swap合约。
Uniswap 闪电兑换
在之前的文章中,我们了解了如何使用 Uniswap 在代币之间实现交换。
在本文中,我们将进一步探讨 闪电兑换。
我们强烈建议你先阅读之前的文章。
与传统贷款不同,闪电贷是在 一次交易 中借入并归还资金。这是必须的。
在 Defi 中,交易者(通常通过机器人)一直在寻找套利机会,以通过在价格不同的平台之间交易同一资产获利。
这正是闪电贷进入图景的地方(尽管通常如此)。
借助 闪电贷,交易者可以借入一大笔资金来执行套利交易。闪电贷和闪电兑换是同一件事。
考虑一种情况,Ethan 从书店以 $10 购买一本书,然后以 $20 将这本书卖给 Jennifer。在这种情况下,Ethan 使用自己的资金购买了书,然后通过出售给 Jennifer 直接将其翻倍。
这就是交易套利的工作方式。
但与 Ethan 不同,在这里我们可以简单地使用闪电贷借入 $10,然后执行交易,类似于卖出书籍,然后偿还贷款(是的,所有这一切都在 1 次交易中完成)。
让我们开始编码我们的闪电兑换合约并测试它!! 😎
在你的 CLI 上使用以下命令初始化项目。
mkdir Flash_swap && cd Flash_swap
npm init -y
现在,安装我们将在项目中使用的必要依赖。
使用下面提供的命令在 CLI 上运行它们。
npm install --save hardhat @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle ethers @uniswap/v2-core dotenv hardhat chai
在你的 CLI 上运行命令 npx hardhat
并创建一个空的 hardhat 配置文件,因为我们将从头开始构建一切。
自定义你的 hardhat 配置:
因为我们要分叉主网来测试闪电兑换。因此,你的 hardhat 配置应类似于以下内容:
注意:将 URL 的 <key>
组件替换为你的个人 Alchemy API 密钥。
另外,如果你对主网分叉不熟悉,请阅读我们关于它的文章,然后再继续本文章。
创建合约和测试的目录以更好地组织代码。
在你的 CLI 中使用以下代码。
mkdir contracts && mkdir tests
为了编写闪电兑换合约,在 contracts 目录中创建一个文件,并命名为 flashswap.sol
编写智能合约:
首先,导入所需的接口并创建一个名为 flashSwap
的合约。
我们将导入 Uniswap 的接口 以使用其功能。你可以通过这个链接 获取接口。
我们还导入了 IUniswapV2Callee 接口。Uniswap 会在我们执行闪电兑换时调用此函数。从技术上讲,这是 Uniswap 将调用的回调函数。
它应类似于以下内容:
接下来,我们创建我们的合约 flashSwap,它继承自 IUniswapV2Callee。Solidity 支持智能合约之间的继承,其中多个合约可以继承到一个合约中。从中其他合约继承特性的合约被称为基合约,而继承特性的合约称为派生合约。
此合约将有 2 个函数:
写 testFlashSwap 函数:
该函数将接受 2 个参数 (A) 我们要从 Uniswap 借用的代币 {地址| 和 (B) 我们想要借用的数量。
注意:在 Uniswap v2 中,所有代币对都包含 WETH 作为其中一种币,因此,要检查某一特定代币是否在 Uniswap 中可用,我们只需检查其与 WETH 的可用性。关于 WETH 的更多信息 → https://weth.io/
address pair = IUniswapV2Factory(UniswapV2Factory).getPair(_tokenBorrow, WETH);
require(pair != address(0), "!pair");
{请注意,我们已经在合约中将 WETH 定义为一个变量。请参考我们完整的合约以了解更多信息。}
在 UniswapV2Pair 中,我们有 2 个代币,token0 和 token1。
我们现在将检查 _tokenBorrow 是否等于 _token0。
如果值相等,则 amount0Out 将是我们传递给函数的 _amount 参数,否则,这将等于零。
同样,我们将对 _ token1 进行相同检查。
address token0 = IUniswapV2Pair(pair).token0();
address token1 = IUniswapV2Pair(pair).token1();
uint256 amount0Out = _tokenBorrow == token0 ? _amount : 0;
uint256 amount1Out = _tokenBorrow == token1 ? _amount : 0;
结果是,你要么会有 amount0Out 等于 _amount 和 amount1Out 等于 0,反之亦然。
然后我们将这些金额值传递到 Uniswap 的交换函数中。
bytes memory data = abi.encode(_tokenBorrow, _amount);
IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), data);
正如你所注意到的,这正是我们调用的在 Uniswap 上执行简单交换的确切函数。请参考我们之前的文章这里。
唯一的区别是最后一个输入。如果它是空的,那么 Uniswap 将尝试执行简单交换。
如果它不是空的,即它有任何数据,那么它将触发 闪电兑换。
为了传递输入,我们将把 tokenBorrow 和 amount 编码为字节,并将其传递给交换函数。
testFlashSwap 函数应类似于:
让我们编写 uniswapV2Call: 🤩
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
// 调用 uniswapv2factory 获取对
address pair = IUniswapV2Factory(UniswapV2Factory).getPair(token0, token1);
require(msg.sender == pair, "!pair");
(address tokenBorrow, uint amount) = abi.decode(_data, (address, uint));
uint fee = ((amount * 3) / 997) + 1;
uint amountToRepay = amount + fee;
IERC20(tokenBorrow).transfer(pair, amountToRepay);
这完成了我们 uniswapV2Call 函数,函数应类似于以下图像:
现在,我们的闪电兑换合约已经完成,应该类似于以下内容:
首先,我们将导入必要的库、ERC20 abi,并创建测试脚本的基本结构。
使用以下参考以更好地理解:
我们还将定义我们要借的金额。
使用以下代码:
const USDCHolder = "0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE";
const USDCAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const borrowAmount = 1000000000; // 由于 USDC 有 6 位小数,因此这对应于 1000 USDC 代币
before
块开始使用我们创建的合约并部署它。before(async () => {
const TestFlashSwapFactory = await ethers.getContractFactory("flashSwap");
TestFlashSwapContract = await TestFlashSwapFactory.deploy();
await TestFlashSwapContract.deployed();
});
A: 首先,我们将模拟我们想要使用的帐户:
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [USDCHolder],
});
const impersonateSigner = await ethers.getSigner(USDCHolder);
然后,我们将定义 USDC 合约
const USDCContract = new ethers.Contract(USDCAddress, ERC20ABI, impersonateSigner)
B: 我们已经知道 Uniswap 对实现闪电兑换收费。因此,我们将计算费用的值。
const fee = Math.round(((borrowAmount * 3) / 997)) + 1;
C: 我们需要理解的是,正因为我们的合约正好在 before
块中部署,它没有任何以太币或任何 USDC。因此,一旦闪电贷实施,该合约必须返回借入的金额加上费用。
因此,我们必须将费用金额从模拟帐户转移到我们合约,以完成交易。
因此,我们将使用以下命令将费用转移给合约:
await USDCContract.connect(impersonateSigner).transfer(TestFlashSwapContract.address, fee)
D: 然后,我们将调用在合约中定义的 testFlashSwap 函数。
await TestFlashSwapContract.testFlashSwap(USDCContract.address, borrowAmount)
E: 现在是检查闪电兑换是否正确执行的时间。为此,我们将检查闪电兑换和我们计算的准确费用支付后,我们合约的余额是否为 0,因为它应该是,还是不是。
const TestFlashSwapContractBalance = await USDCContract.balanceOf(TestFlashSwapContract.address)
expect(TestFlashSwapContractBalance.eq(BigNumber.from("0"))).to.be.true;
你的最终测试脚本应如下所示:
最后,在你的 CLI 中使用命令 npx hardhat test tests/flashswaptest
运行你的测试
结果应如下所示:
瞧 🥳🥳🥳 我们的 闪电兑换以优异的表现通过了 🤣
闪电兑换允许你在 Uniswap 上借入任何 ERC20 代币并执行任何代码逻辑,只要你在 同一交易中 归还相同的代币或任何其他等值代币加上费用。
希望你喜欢我们的努力,尝试自行运行这个。 :)
当然!如果你遇到任何错误,可以通过标记 @uv_labs 在 Twitter 上问我们。
再一次,我们刚刚运行的所有代码在这里 👉 Github repository。如果你喜欢我们的工作,请给我们一个星标和点赞。
作者(欢迎反馈):👇
- 原文链接: medium.com/buildbear/fla...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!