学习如何自主执行Uniswap的闪电兑换⚡

本文介绍了Uniswap的Flash Swap概念及其实现方法,详细讲解了Flash Loan的工作原理,包括如何利用Flash Loan进行套利交易,并提供了代码示例和测试脚本,让读者能够自主实现Flash Swap合约。

Uniswap 闪电兑换

之前的文章中,我们了解了如何使用 Uniswap 在代币之间实现交换。

在本文中,我们将进一步探讨 闪电兑换

我们强烈建议你先阅读之前的文章。

什么是闪电贷 / 闪电兑换?

与传统贷款不同,闪电贷是在 一次交易 中借入并归还资金。这是必须的。

Defi 中,交易者(通常通过机器人)一直在寻找套利机会,以通过在价格不同的平台之间交易同一资产获利。

这正是闪电贷进入图景的地方(尽管通常如此)。

借助 闪电贷,交易者可以借入一大笔资金来执行套利交易。闪电贷和闪电兑换是同一件事。

闪电贷套利如何运作?

考虑一种情况,Ethan 从书店以 $10 购买一本书,然后以 $20 将这本书卖给 Jennifer。在这种情况下,Ethan 使用自己的资金购买了书,然后通过出售给 Jennifer 直接将其翻倍。

这就是交易套利的工作方式。

但与 Ethan 不同,在这里我们可以简单地使用闪电贷借入 $10,然后执行交易,类似于卖出书籍,然后偿还贷款(是的,所有这一切都在 1 次交易中完成)。

让我们开始编码我们的闪电兑换合约并测试它!! 😎

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

2. 初始化你的 Hardhat 项目

在你的 CLI 上运行命令 npx hardhat 并创建一个空的 hardhat 配置文件,因为我们将从头开始构建一切。

自定义你的 hardhat 配置:

因为我们要分叉主网来测试闪电兑换。因此,你的 hardhat 配置应类似于以下内容:

注意:将 URL 的 <key> 组件替换为你的个人 Alchemy API 密钥。

另外,如果你对主网分叉不熟悉,请阅读我们关于它的文章,然后再继续本文章。

3. 为闪电兑换编写智能合约

创建合约和测试的目录以更好地组织代码。

在你的 CLI 中使用以下代码。

mkdir contracts && mkdir tests

为了编写闪电兑换合约,在 contracts 目录中创建一个文件,并命名为 flashswap.sol

编写智能合约:

首先,导入所需的接口并创建一个名为 flashSwap 的合约。

我们将导入 Uniswap 的接口 以使用其功能。你可以通过这个链接 获取接口。

我们还导入了 IUniswapV2Callee 接口。Uniswap 会在我们执行闪电兑换时调用此函数。从技术上讲,这是 Uniswap 将调用的回调函数。

它应类似于以下内容:

接下来,我们创建我们的合约 flashSwap,它继承自 IUniswapV2Callee。Solidity 支持智能合约之间的继承,其中多个合约可以继承到一个合约中。从中其他合约继承特性的合约被称为基合约,而继承特性的合约称为派生合约。

此合约将有 2 个函数:

  • testFlashSwap():这是我们用来调用以触发闪电兑换交易的函数。
  • uniswapV2Call(): 这是 Uniswap 合约将调用的函数

testFlashSwap 函数:

该函数将接受 2 个参数 (A) 我们要从 Uniswap 借用的代币 {地址| 和 (B) 我们想要借用的数量。

  • 首先,我们将检查 _ tokenBorrowWETH 的配对合约是否存在。我们可以通过调用 getPair 函数在 UniswapV2Factory 上做到这一点。

注意:在 Uniswap v2 中,所有代币对都包含 WETH 作为其中一种币,因此,要检查某一特定代币是否在 Uniswap 中可用,我们只需检查其与 WETH 的可用性。关于 WETH 的更多信息 → https://weth.io/

address pair = IUniswapV2Factory(UniswapV2Factory).getPair(_tokenBorrow, WETH);
require(pair != address(0), "!pair");

{请注意,我们已经在合约中将 WETH 定义为一个变量。请参考我们完整的合约以了解更多信息。}

在 UniswapV2Pair 中,我们有 2 个代币,token0token1

我们现在将检查 _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 将尝试执行简单交换。

如果它不是空的,即它有任何数据,那么它将触发 闪电兑换

为了传递输入,我们将把 tokenBorrowamount 编码为字节,并将其传递给交换函数。

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");
  • 解码数据: 然后,我们将解码 Uniswap 传递给我们的数据,这不是强制性步骤,但鼓励这样做。
(address tokenBorrow, uint amount) = abi.decode(_data, (address, uint));
  • 计算费用: Uniswap 对任何形式的转换收取 0.3% 的费用。使用以下代码,我们计算合约在进行闪电兑换时需要承担的费用。
uint fee = ((amount * 3) / 997) + 1;
uint amountToRepay = amount + fee;
  • 还款: 最后,我们必须向 Uniswap 还款所借的代币以及费用。这可以通过以下代码实现。
IERC20(tokenBorrow).transfer(pair, amountToRepay);

这完成了我们 uniswapV2Call 函数,函数应类似于以下图像:

现在,我们的闪电兑换合约已经完成,应该类似于以下内容:

4. 为我们的合约编写一些测试脚本

首先,我们将导入必要的库、ERC20 abi,并创建测试脚本的基本结构。

使用以下参考以更好地理解:

  • 现在,我们将定义我们要模拟的合约地址和 USDC 地址。

我们还将定义我们要借的金额。

使用以下代码:

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。如果你喜欢我们的工作,请给我们一个星标和点赞。

作者(欢迎反馈):👇

Amateur-DevPari Tomar

  • 原文链接: medium.com/buildbear/fla...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
tomarpari90
tomarpari90
江湖只有他的大名,没有他的介绍。