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 计算了这个数额!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/732838.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Appium问题】每次启动appium都会安装一次uiautomator

问题 每次启动appium&#xff0c;都需要安装一次uiautomator2比较麻烦 解决 在配置文件capabilities 中增加参数skipServerInstallationTrue

【C++专栏】C++入门 | 函数重载、引用、内联函数

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;C专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ C入门 | 函数重载、引用、内联函数 文章编号&#xff1a;C入门 / 02 文…

mongodb的备份与恢复

查询语句 show dbs use leshusanguo show tables db.user.find() db.user.find({},{uid:1,nickname:1,max_level:1}) db.user.find({max_level:1},{uid:1,nickname:1,max_level:1}) db.user.count() db.user.find({max_level:1,uid:672064,},{uid:1,nickname:1,max_level:1}) …

Docker的奇幻漂流

航海日志&#xff1a;Docker的奇幻漂流 欢迎各位探险家们&#xff0c;今天我们将启航进入一个由容器构成的神秘海域——Docker。在这个由代码构建的奇妙世界里&#xff0c;我们将学习如何驾驭这些名为“容器”的神奇船只。准备好了吗&#xff1f;让我们扬帆起航&#xff0c;探…

ORACLE RAC反应卡顿时enq: SV - contention和latch: row cache objects的分析

某客户数据库系统使用ORACLE RAC 11G版本&#xff0c;两个节点。在上午8点钟之后&#xff0c;业务开始大量进行时&#xff0c;出现严重的卡顿问题&#xff1b;在工程师分析后&#xff0c;发现当时出现了很多异常等待数据&#xff0c;如典型的enq: SV - contention 、enq: TX - …

基于单片机的老人防丢系统设计

目 录 摘 要 I Abstract II 引 言 3 1 系统总体架构 6 1.1方案设计与选择 6 1.2 系统架构设计 6 1.3 系统器件选择 7 2 系统硬件设计 9 2.1 单片机外围电路设计 9 2.2 LCD1602液晶显示电路设计 12 2.3 短信模块电路设计 14 2.4 GPS模块电路设计 14 2.5 电源与按键控制电路设计…

用 reduce 实现 map 的功能

Array.prototype.map function (callback) {const array this;return array.reduce((acc, cur, index) > {acc.push(callback(cur, index, array));return acc;}, []); }; 测试&#xff1a; var m [1, 2, 3, 4, 5].map(function (v, i, arr) {return v v; }); console…

精读《深度学习 - 函数式之美》

1 引言 函数式语言在深度学习领域应用很广泛&#xff0c;因为函数式与深度学习模型的契合度很高&#xff0c;The Beauty of Functional Languages in Deep Learning — Clojure and Haskell 就很好的诠释了这个道理。 通过这篇文章可以加深我们对深度学习与函数式编程的理解…

使用Python合并PDF文件并添加自定义目录及页脚

如何用Python合并PDF文件并添加自定义目录及页脚 步骤一&#xff1a;准备环境步骤二&#xff1a;合并PDF文件步骤三&#xff1a;处理特定文件步骤四&#xff1a;合并并添加目录与页脚步骤五&#xff1a;保存最终文档结语完整代码 在处理文档时&#xff0c;我们经常遇到需要合并…

Mybaties-Plus saveBatch()、自定义批量插入、多线程批量插入性能测试和对比

一.背景 最近在做一个项目的时候&#xff0c;由于涉及到需要将一个系统的基础数据全量同步到另外一个系统中去&#xff0c;结果一看&#xff0c;基础数据有十几万条&#xff0c;作为小白的我&#xff0c;使用单元测试&#xff0c;写了一段代码&#xff0c;直接采用了MP(Mybati…

element---tree树形结构(返回的数据与官方的不一样)

项目中要用到属性结构数据&#xff0c;后端返回的数据不是官方默认的数据结构&#xff1a; <el-tree:data"treeData":filter-node-method"filterNode":props"defaultProps"node-click"handleNodeClick"></el-tree>这是文档…

SpringCloudGateway全局过滤器

文章目录 全局过滤器的作用自定义全局过滤器过滤器执行的顺序 上一篇 Gateway理论与实践 介绍的过滤器&#xff0c;网关提供了31种&#xff0c;但每一种过滤器的作用都是固定的。如果我们希望拦截请求&#xff0c;做自己的业务逻辑则没办法实现。 全局过滤器的作用 全局过滤器的…

怎样添加、移除、移动、复制、创建和查找节点?

1&#xff09;创建新节点 createDocumentFragment() //创建一个 DOM 片段createElement() //创建一个具体的元素createTextNode() //创建一个文本节点 2&#xff09;添加、移除、替换、插入 appendChild() //添加removeChild() //移除replaceChild() //替换insertBefore() /…

高级语言讲义2010计专(仅高级语言部分)

1.编写一程序&#xff0c;对输入的正整数&#xff0c;求他的约数和。 如&#xff1a;18的约数和为1236939 #include <stdio.h>int getsum(int n){int i,sum0;for(i1;i<n;i)if(n%i0)sumi;return sum; } int main(){int sum getsum(18);printf("%d",sum); …

JS直接量及其相关对象

什么是直接量 直接量是指不需要创建对象就可以直接使用的变量。ES中的直接量主要有三种类型&#xff1a;表示字符串的string类型、表示数字的number类型和表示true/false的boolean类型。当我们直接将值赋给变量后&#xff0c;ES就会自动判断其类型&#xff0c;而且当参数发生变…

Android14之编译输出system/product/vendor/odm分区(一百九十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

机器学习流程—数据预处理 Encoding

机器学习流程—数据预处理 Encoding 在机器学习中,我们经常会遇到分类变量,这些分量变量往往机器学习模型没有办法从中学习,往往有两种,一种是字符型,一种是数值型。通常需要对分类型变量做一些处理,常用的方法有两种:label encoding和one hot encoding。 例如,假设数…

「AI工程师」数据处理与分析-工作指导

工作指导书 一、工作职责 负责数据的收集、清洗、整合和处理,确保数据质量和准确性。进行数据分析和挖掘,提取有价值的信息,为业务决策提供支持。构建和维护数据处理和分析的流程和工具,提高数据处理效率。与其他团队成员合作,共同解决数据处理和分析过程中遇到的问题。二…

深度学习相关概念及术语总结

目录 1.CNN2.RNN3.LSTM4.NLP5.CV6.正向传播7.反向传播8.sigmoid 函数9.ReLU函数10.假设函数11.损失函数12.代价函数 1.CNN CNN 是卷积神经网络&#xff08;Convolutional Neural Network&#xff09;的缩写。卷积神经网络是一种深度学习模型&#xff0c;专门用于处理具有网格状…

kotlin筑基(2)

文章目录 一、internal二、infix三、object四、open五、operator六、sealed class1. 用法一2. 用法二 七、typealias八、when1. 带参数2. 不带参数 一、internal internal 修饰方法&#xff0c;表示这个方法只适合当前 module 使用&#xff0c;同理修饰 class 也是一样。 Syn…