前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何在Uniswap上执行闪电兑换(Flash Swaps)

如何在Uniswap上执行闪电兑换(Flash Swaps)

作者头像
Tiny熊
发布2022-11-07 09:57:15
1.9K0
发布2022-11-07 09:57:15
举报
文章被收录于专栏:深入浅出区块链技术

译文出自:登链翻译计划[1] 译者:翻译小组[2] 校对:Tiny 熊[3]

Uniswap Flash Swap

在上一篇[4]文章中,我们了解了如何使用 Uniswap 实现代币之间的兑换。

在这篇文章中,我们将通过探讨闪电兑换(Flash Swaps)来向前推进一个层次。

在阅读本文之前,强烈建议你阅读之前之前的文章了解 Uniswap 的运行规则。

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

与传统贷款不同,在闪电贷中,在一个交易里完成资金借入和归还。这一点是必须的。

Defi里,交易者(通常通过机器人)不断寻找套利机会,通过在为同一资产提供不同价格的平台之间进行交易来获得利益。

这就是闪电贷出现的地方(通常是)。

闪电贷的帮助下,交易者可以借到一大笔钱来执行套利交易。闪电贷和闪电兑换其实是一回事。

闪电贷套利是如何运作的?

考虑一种情况,Ethan 从书店花 10 美元买了一本书,然后以 20 美元的价格将这本书卖给 Jennifer。在这种情况下,Ethan 用自己的钱买了一本书,然后通过卖给 Jennifer 直接翻倍。

这正是交易套利的运作方式。

但与 Ethan 用自己的钱从书店买书不同的是,在这里,我们可以简单地使用闪电贷借 10 美元,然后执行交易,类似于卖书,然后偿还贷款(是的,所有这些都在一个交易中)。

让我们潜心编写我们自己的 Flash 兑换合约,并进行测试吧!😎

1. 创建项目并安装依赖

在命令行终端(CLI)上使用以下命令来初始化项目:

代码语言:javascript
复制
mkdir Flash_swap && cd Flash_swap
npm init -y

现在,安装我们将在项目中使用的依赖项。

使用下面提供的命令,在命令行终端上运行来安装它们:

代码语言:javascript
复制
npm install --save hardhat @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle ethers @uniswap/v2-core dotenv hardhat chai

2. 初始化 Hardhat 项目

在你的 CLI 上运行 npx hardhat 命令,并创建一个空的 hardhat 配置文件(然后定制 Hardhat 配置),因为我们要从头开始构建一切。

因为我们要主网的 fork 来测试 Flashswap。因此,你的 Hardhat 配置应该看起来类似于这样:

注意:用你自己的Alchemy[5]API 密钥替换 URL 中的<key>部分。

另外,如果你不了解主网 fork,请阅读我们的这篇文章[6],然后再跟着这篇文章走。

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

为合约和测试创建新目录,以便更好地组织代码,在你的 CLI 中使用以下代码:

代码语言:javascript
复制
mkdir contracts && mkdir tests

为了编写 flash swap 合约,在合约目录内创建一个文件,并将其命名为flashswap.sol

编写智能合约:

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

我们将导入Uniswap的接口,以使用其功能。你可以使用这个链接[7]获得该接口文件。

我们还导入了IUniswapV2Callee接口。当我们执行 flash swap 时,Uniswap 将调用这个函数。技术上来说,这是 Uniswap 将调用的回调函数。

之后代码看起来应该类似于这样:

接下来,编写我们的flashSwap合约,它继承自IUniswapV2Callee。Solidity 支持智能合约之间的继承,多个合约可以被一个合约继承。从中继承功能的合约被称为基础合约,而继承功能的合约被称为派生合约。

这个合约将有 2 个功能:

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

现在编写testFlashSwap 函数:

这个函数将接受 2 个参数:(A)要从 Uniswap 借入的代币{地址}和(B)我们想借入的金额。

  • 我们要做的第一件事是,检查_tokenBorrowWETH的配对合约是否存在。我们可以通过调用UniswapV2Factory 的getPair*函数来做到这一点。

注意:在 Uniswap v2 中,所有的代币对都是以 WETH(代替 ETH)作为其中一个币种,因此,要检查一个特定的代币时候与 ETH 在 Uniswap 上具有配对,我们只需检查它与 WETH 的配对即可。更多关于什么是 WETH → https://weth.io/

代码语言:javascript
复制
address pair = IUniswapV2Factory(UniswapV2Factory).getPair(_tokenBorrow, WETH);
require(pair != address(0), "!pair");

_请注意,我们在合约中已经将 WETH 定义为一个变量。请参考文章最后的完整合约_。

在 UniswapV2Pair 中有两个代币,token0token1

我们检查*_tokenBorrow是否等于_token0*,如果相等,那么amount0Out将是我们传递给函数的_amount 参数,否则,它将等于 0。

同样地,我们将对*_token1*进行同样的检查。

代码语言:javascript
复制
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 的 swap 函数中传递这些数值:

代码语言:javascript
复制
bytes memory data = abi.encode(_tokenBorrow, _amount);
IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), data);

你会注意到,这和我们在 Uniswap 上执行简单兑换时调用的函数完全一样。请参考我们之前的文章here.[8]

唯一的区别是最后一个参数。如果它是空的,那么 Uniswap 将尝试执行一个简单的兑换。

如果最后一个参数不是空的,而是有附加数据,那么它将会触发一个闪电兑换

为了传递输入参数,我们将把tokenBorrowamount编码为字节,然后传递给 swap 函数。

testFlashSwap函数看起来应该类似于:

现在我们来写一下uniswapV2Call:

  • _基本的权限处理_:现在,任何人都可以访问这个函数,因此我们要做的第一件事是检查这个函数只能由 LP 配对合约(即 LP 合约)调用。因此还将检查调用者(msg.sender)是否等于配对合约。为此,使用下面的代码:
代码语言:javascript
复制
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
// call uniswapv2factory to getpair
address pair = IUniswapV2Factory(UniswapV2Factory).getPair(token0, token1);
require(msg.sender == pair, "!pair");
  • _解码数据_:然后,需要将 Uniswap 传递给我们的数据进行解码(也是之前传递给 Uniswap 的数据),这不是一个强制性的步骤,但鼓励这样做。
代码语言:javascript
复制
(address tokenBorrow, uint amount) = abi.decode(_data, (address, uint));
  • _计算费用_:Uniswap 对任何形式的兑换收取 0.3%的费用。使用下面的代码正在计算我们的合约在进行闪电兑换时需要承担的费用:
代码语言:javascript
复制
uint fee = ((amount * 3) / 997) + 1;
uint amountToRepay = amount + fee;
  • _还款_:最后,我们必须向 Uniswap 支付所借的代币,包括费用。它可以通过使用以下代码来实现:
代码语言:javascript
复制
IERC20(tokenBorrow).transfer(pair, amountToRepay);

这就完成了我们的uniswapV2Call函数,它看起来类似于下面:

现在,我们的 flashSwap 合约已经完成,应该类似于这样:

在本文的例子中,为了简化,uniswapV2Call 函数中并没有使用收到的代币进行套利操作,读者可以根据需要在uniswapV2Call中调用其他的 DEX 进行套利。

为合约写一些测试脚本

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

  • 现在,我们将定义要冒充(模拟)的地址和 USDC 地址。

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

使用下面的方法:

代码语言:javascript
复制
const USDCHolder = "0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE";
const USDCAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const borrowAmount = 1000000000; // 因为 USDC 是 6 为小数,对应 1000 USDC
  • 接下来,我们将从before块开始使用我们创建的合约并部署它。
代码语言:javascript
复制
before(async () => {
        const TestFlashSwapFactory = await ethers.getContractFactory("flashSwap");
        TestFlashSwapContract = await TestFlashSwapFactory.deploy();
        await TestFlashSwapContract.deployed();
});
  • 最后,我们编写测试脚本来检查闪电兑换的执行情况。

A:首先,我们将冒充要使用的账户:

代码语言:javascript
复制
await hre.network.provider.request({
            method: "hardhat_impersonateAccount",
            params: [USDCHolder],
});
const impersonateSigner = await ethers.getSigner(USDCHolder);

然后,定义 USDC 合约:

代码语言:javascript
复制
const USDCContract = new ethers.Contract(USDCAddress, ERC20ABI, impersonateSigner)

B: 我们已经知道,Uniswap 对实现 Flash Swap 收取一定的费用。因此需要计算该费用:

代码语言:javascript
复制
const fee = Math.round(((borrowAmount * 3) / 997)) + 1;

C:我们需要了解,由于合约刚刚在before代码区块中得到部署,它没有任何以太币或任何 USDC。那么,一旦闪电借款被执行,合约就必须归还所借的金额和费用。

因此,我们必须把费用的金额从冒充的账户转移到我们的合约,以便完成交易。

因此,我们将使用以下代码将费用转移到合约中。

代码语言:javascript
复制
await USDCContract.connect(impersonateSigner).transfer(TestFlashSwapContract.address, fee)

D: 然后,调用在合约中定义的testFlashSwap函数。

代码语言:javascript
复制
await TestFlashSwapContract.testFlashSwap(USDCContract.address, borrowAmount)

E: 现在是时候检查执行的 Flashswap 是否正确了。为此,我们将检查合约余额,在 Flashswap 和支付我们计算的确切费用后,是否为 0,因为它应该是这样的。

代码语言:javascript
复制
const TestFlashSwapContractBalance = await USDCContract.balanceOf(TestFlashSwapContract.address)
expect(TestFlashSwapContractBalance.eq(BigNumber.from("0"))).to.be.true;

你的最终测试脚本应该是这样的。

最后,在 CLI 中使用npx hardhat test tests/flashswaptest命令运行测试。

结果应该是这样的:

到这里,我们的闪电兑换测试就通过。

总结

闪电兑换允许在 Uniswap 上借用任何 ERC20 代币并执行任何代码逻辑(在 uniswapV2Call 中),只要你在同一笔交易中偿还相同的代币或相同价值的任何其他代币以及费用。

希望你可以在本文的基础上,并尝试自己运行闪电兑换进行更多的交易:)

同样,本文运行的所有代码都在这里Github 仓库[9]


本翻译由 Duet Protocol[10] 赞助支持。

原文:https://medium.com/uv-labs/flash-swap-5bcdbd9aaa14

参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

翻译小组: https://learnblockchain.cn/people/412

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

上一篇: https://learnblockchain.cn/article/4197

[5]

Alchemy: https://alchemy.com/?r=7d60e34c-b30a-4ffa-89d4-3c4efea4e14b

[6]

这篇文章: https://medium.com/uv-labs/fork-the-f-ing-ethereum-blockchain-transfer-tokens-from-vitaliks-account-46d408f7356c

[7]

链接: https://github.com/UV-Labs/Tutorials/blob/main/Flash_swap/contracts/interfaces/Uniswap.sol

[8]

here.: https://medium.com/uv-labs/uniswap-testing-1d88ca523bf0

[9]

Github仓库: https://github.com/UV-Labs/Tutorials

[10]

Duet Protocol: https://duet.finance/?utm_souce=learnblockchain

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是闪电贷/闪电兑换?
  • 闪电贷套利是如何运作的?
    • 1. 创建项目并安装依赖
      • 2. 初始化 Hardhat 项目
        • 3. 为闪电兑换写一个智能合约
        • 为合约写一些测试脚本
          • 总结
            • 参考资料
            相关产品与服务
            区块链
            云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档