当前位置: 首页 > news >正文

Solidity Uniswap V2 Pair中交换Token

        交换意味着使用一定数量的TokenA来换取Tokenb。但我们需要一些额外的辅助服务:

        1.提供实际汇率。

        2.保证所有的交易都是在正确的汇率下进行的。

        GitHub - XuHugo/solidityproject: DApp go go go !!!

        我们在研究流动性供应时学到了一些关于 DEX 定价的知识:决定汇率的是池中的流动性数量。成功互换的主要条件:互换后的reserve乘积必须等于或大于互换前的reserve乘积。无论Pool中的reserve数量是多少,恒等乘积都必须保持不变。这基本上是我们必须保证的唯一条件,而且令人惊讶的是,这个条件让我们无需计算互换价格。

        正如我在介绍中提到的,Pair合约是一个核心合约,这意味着它的功能必须尽可能底层化和最小化。这也影响到我们如何向合约发送代币。有两种方法可以将代币转移给他人:

        1、调用Token合约的transfer方法,并传递接收者的地址和要发送的金额。

        2、调用 approve 方法,将一定数量的代币授权,允许其他用户或合约使用。其他人必须调用 transferFrom 才能转移你的Token。你只需为批准一定数量的Token支付费用,而对方则需为实际转账支付费用。

        调用 approve 模式在以太坊应用中非常常见:dapp 要求用户批准最大金额的消费,这样用户就不需要一次又一次地调用批准。这可以改善用户体验。而这并不是我们目前需要考虑的,因此,我们将采用手动转入Pair合约的方式。

        函数入参需要两个输出金额,每个token一个。这些是调用者希望用token换取的金额。为什么要这样做呢——需要两个token?因为我们不想强制执行交换的方向:调用者可以指定任一个金额或两个金额,我们只需执行必要的检查。

function swap(uint256 amount0Out,uint256 amount1Out,address to) public {if (amount0Out == 0 && amount1Out == 0)revert InsufficientOutputAmount();...

接下来,我们需要确保有足够的reserve发送给用户。

    ...(uint112 reserve0_, uint112 reserve1_, ) = getReserves();if (amount0Out > reserve0_ || amount1Out > reserve1_)revert InsufficientLiquidity();...

        在获得reserve并进行预检查后,我们要做的第一件事就是将token转移给用户。有趣的是,我们可以提前做这件事,反而是未了更安全,后边我们会介绍原因,也许你现在就知道了原因。转账完成后,我们再计算输入金额:

if (amount0Out > 0) _safeTransfer(token0, to, amount0Out);if (amount1Out > 0) _safeTransfer(token1, to, amount1Out);uint256 balance0 = IERC20(token0).balanceOf(address(this));uint256 balance1 = IERC20(token1).balanceOf(address(this));uint256 amount0In = balance0 > reserve0 - amount0Out? balance0 - (reserve0 - amount0Out): 0;uint256 amount1In = balance1 > reserve1 - amount1Out? balance1 - (reserve1 - amount1Out): 0;if (amount0In == 0 && amount1In == 0) revert InsufficientInputAmount();

        为了让这部分的逻辑更清晰,我们可以把 reserve0 和 reserve1 看作 "旧余额",即交换开始前合约的余额。

        在交换token时,我们通常会提供 amount0Out 或 amount1Out。因此,通常会有 amount0In 或 amount1In。但这里允许我们同时设置 amount0Out 和 amount1Out,因此 amount0In 和 amount1In 也有可能都大于零。但如果这两个值都为零,用户就没有向合约发送任何Token,这是不允许的。

        因此,在这几行中,我们发现了新的余额:它们不包括输出金额,但包括输入金额。

        然后就是我们之前讨论过的常数乘积检验。我们预计这个合约token的余额与其reserve不同,我们需要确保它们的乘积等于或大于当前储备的乘积。如果满足此要求,则:

        1.调用方正确计算了汇率(包括滑点)。

        2.输出量正确。

        3.转到合约中的金额也是正确的。

uint256 balance0Adjusted = (balance0 * 1000) - (amount0In * 3);uint256 balance1Adjusted = (balance1 * 1000) - (amount1In * 3);if (balance0Adjusted * balance1Adjusted <uint256(reserve0_) * uint256(reserve1_) * (1000**2)) revert InvalidK();

        首先,我们计算调整后的余额:即当前余额减去swap fee,后者适用于输入金额。同样,由于整除的原因,我们必须将余额乘以 1000,金额乘以 3,以 "模拟 "输入金额乘以 0.003(0.3%)。

        接下来,我们要为调整后的余额计算一个新的 K,并将其与当前的 K 进行比较。为了补偿调整后余额乘以 1000 的结果,我们将旧储备金乘以 1000 * 1000。

        基本上,我们是用新余额减去掉期费来计算新的 K 值。这个新 K 必须大于或等于旧 K。

让我们测试一下,当我们试图获取过多的输出代币时,是否会出现 InvalidK 错误:

function testSwapUnpaidFee() public {token0.transfer(address(pair), 1 ether);token1.transfer(address(pair), 2 ether);pair.mint(address(this));token0.transfer(address(pair), 0.1 ether);vm.expectRevert(encodeError("InvalidK()"));pair.swap(0, 0.181322178776029827 ether, address(this), "");}

        在这里,我们试图用 0.181322178776029827 ether 的 token1 交换 0.1 ether 的 token0,但失败了。如果将代币 1 的金额减少 1,测试就会通过。我使用 getAmountOut 计算了这个数额!

相关文章:

  • 图文并茂的讲清楚Linux零拷贝技术
  • 地球系统模式(CESM)
  • mysql的其他问题
  • 创邻科技获评环紫金港创新生态圈智源创新企业
  • 计算机网络-第5章 运输层(1)
  • 【字典合集】SecLists-更全面的渗透测试字典 v2024.1
  • 软件设计模式:模板方法模式
  • [论文笔记]DouZero: Mastering DouDizhu with Self-Play Deep Reinforcement Learning
  • 【Linux的网络编程】
  • Linux删除Mysql
  • MinGW-w64的下载与安装
  • 03:HAL---中断
  • 性能优化-卡牌项目渲染优化
  • QML | 在QML中导入JavaScript资源、导入JavaScript资源、包含一个JavaScript 资源
  • 机器学习之分类回归模型(决策数、随机森林)
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • DataBase in Android
  • ES6系列(二)变量的解构赋值
  • express如何解决request entity too large问题
  • jQuery(一)
  • 工程优化暨babel升级小记
  • 记一次和乔布斯合作最难忘的经历
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 使用agvtool更改app version/build
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 优化 Vue 项目编译文件大小
  • 转载:[译] 内容加速黑科技趣谈
  • 扩展资源服务器解决oauth2 性能瓶颈
  • #NOIP 2014# day.1 T2 联合权值
  • %check_box% in rails :coditions={:has_many , :through}
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (一)基于IDEA的JAVA基础10
  • (一)为什么要选择C++
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • .bat批处理(一):@echo off
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .NET序列化 serializable,反序列化
  • /proc/vmstat 详解
  • ?.的用法
  • @AutoConfigurationPackage的使用
  • @WebServiceClient注解,wsdlLocation 可配置
  • [AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现
  • [BT]BUUCTF刷题第8天(3.26)
  • [BZOJ 1032][JSOI2007]祖码Zuma(区间Dp)
  • [bzoj1038][ZJOI2008]瞭望塔
  • [BZOJ2208][Jsoi2010]连通数
  • [HCTF 2018]WarmUp (代码审计)
  • [Hive] 常见函数
  • [HNOI2008]Cards
  • [IE技巧] 如何让IE 启动的时候不加载任何插件