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

公司做网站会计凭证怎么做青青河边草直播免费观看

公司做网站会计凭证怎么做,青青河边草直播免费观看,保定企业网站开发,响应式大学网站本文首发于公众号:Keegan小钢 SwapRouter 合约封装了面向用户的交易接口,但不再像 UniswapV2Router 一样根据不同交易场景拆分为了那么多函数,UniswapV3 的 SwapRouter 核心就只有 4 个交易函数: exactInputSingle:指…

本文首发于公众号:Keegan小钢


SwapRouter 合约封装了面向用户的交易接口,但不再像 UniswapV2Router 一样根据不同交易场景拆分为了那么多函数,UniswapV3 的 SwapRouter 核心就只有 4 个交易函数:

  • exactInputSingle:指定输入数量的单池内交易
  • exactOutputSingle:指定输出数量的单池内交易
  • exactInput:指定输入数量和交易路径的交易
  • exactOutput:指定输出数量和交易路径的交易

Single 的只支持单池内的交易,而不带 Single 的则支持跨不同池子的互换交易。

exactInputSingle

先来看简单的单池交易,以 exactInputSingle 为始,其代码实现如下:

struct ExactInputSingleParams {address tokenIn;   //输入tokenaddress tokenOut;  //输出tokenuint24 fee;        //手续费率address recipient; //收款地址uint256 deadline;  //过期时间uint256 amountIn;  //指定的输入token数量uint256 amountOutMinimum;  //输出token的最小数量uint160 sqrtPriceLimitX96; //限定的价格
}function exactInputSingle(ExactInputSingleParams calldata params)externalpayableoverridecheckDeadline(params.deadline)returns (uint256 amountOut)
{amountOut = exactInputInternal(params.amountIn,params.recipient,params.sqrtPriceLimitX96,SwapCallbackData({path: abi.encodePacked(params.tokenIn, params.fee, params.tokenOut), payer: msg.sender}));require(amountOut >= params.amountOutMinimum, 'Too little received');
}

其入参有 9 个参数,返回值就一个 amountOut,即输出的 token 数量。

从代码上可看出,实际的逻辑实现是在内部函数 exactInputInternal。查看该内部函数之前,我们先来了解下 SwapCallbackData。我们从上面代码可以看到,调用 exactInputInternal 时,最后一个传入的参数就是 SwapCallbackData,这其实是一个结构体,定义了两个属性:

struct SwapCallbackData {bytes path;address payer;
}

path 表示交易路径,在以上代码中,就是由 tokenInfeetokenOut 这三个变量拼接而成。payer 表示支付输入 token 的地址,上面的就是 msg.sender

接着,来看看内部函数 exactInputInternal 的代码实现:

function exactInputInternal(uint256 amountIn,address recipient,uint160 sqrtPriceLimitX96,SwapCallbackData memory data
) private returns (uint256 amountOut) {// allow swapping to the router address with address 0if (recipient == address(0)) recipient = address(this);//从路径中解码出第一个池子(address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool();//当tokenIn<tokenOUt时,则说明tokenIn为token0,所以是要将token0兑换成token1bool zeroForOne = tokenIn < tokenOut;//调用底层池子的swap函数执行交易(int256 amount0, int256 amount1) =getPool(tokenIn, tokenOut, fee).swap(recipient,zeroForOne,amountIn.toInt256(),sqrtPriceLimitX96 == 0? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1): sqrtPriceLimitX96,abi.encode(data));//返回amountOutreturn uint256(-(zeroForOne ? amount1 : amount0));
}

首先,如果 recipient 地址为零地址的话,那会把 recipient 重置为当前合约地址。

接着,通过 data.path.decodeFirstPool() 从路径中解码得出 tokenIntokenOutfeedecodeFirstPool 函数是在库合约 Path 里实现的。

布尔类型的 zeroForOne 表示底层 token0token1 的兑换方向,为 true 表示用 token0 兑换 token1false 则反之。因为底层的 token0 是小于 token1 的,所以,当 tokenIn 也小于 tokenOut 的时候,说明 tokenIn == token0,所以 zeroForOnetrue

然后,通过 getPool 函数可得到池子地址,再调用底层池子的 swap 函数来执行实际的交易逻辑。

最后,我们要得到的是 amountOut,这是 amount0 和 amount1 中的其中一个。我们已经知道,zeroForOnetrue 的时候,tokenIn 等于 token0,所以 tokenOut 就是 token1,因此 amountOut 就是 amount1。另外,对底层池子来说,属于输出的时候,返回的数值是负数,即 amount1 其实是一个负数,因此需要再加个负号转为正数的 uint256 类型。

在这个函数里,我们可以看出并没有支付 token 的功能,但前面讲解 UniswapV3Pool 时已经了解到,支付是在回调函数 uniswapV3SwapCallback 里完成的。因为这个回调函数会涉及到所有 4 种交易类型,所以我们留到最后再来讲解。

exactOutputSingle

接着,来看 exactOutputSingle 函数的实现,其代码如下:

struct ExactOutputSingleParams {address tokenIn;   //输入tokenaddress tokenOut;  //输出tokenuint24 fee;        //手续费率address recipient; //收款地址uint256 deadline;  //过期时间uint256 amountOut; //指定的输出token数量uint256 amountInMaximum;   //输入token的最大数量uint160 sqrtPriceLimitX96; //限定的价格
}function exactOutputSingle(ExactOutputSingleParams calldata params)externalpayableoverridecheckDeadline(params.deadline)returns (uint256 amountIn)
{// avoid an SLOAD by using the swap return dataamountIn = exactOutputInternal(params.amountOut,params.recipient,params.sqrtPriceLimitX96,SwapCallbackData({path: abi.encodePacked(params.tokenOut, params.fee, params.tokenIn), payer: msg.sender}));require(amountIn <= params.amountInMaximum, 'Too much requested');// has to be reset even though we don't use it in the single hop caseamountInCached = DEFAULT_AMOUNT_IN_CACHED;
}

可看出,exactOutputSingle 函数的实现与 exactInputSingle 函数大同小异。首先,参数上,只有两个不同,exactInputSingle 函数指定的是 amountInamountOutMinimum;而 exactOutputSingle 函数改为了 amountOutamountInMaximum,即输出是指定的,而输入则限制了最大值。其次,实际逻辑封装在了 exactOutputInternal 内部函数,而且传给该内部函数的最后一个参数的 path 组装顺序也不一样了,排在第一位的是 tokenOut

核心实现还是在 exactOutputInternal 内部函数,其代码实现如下:

function exactOutputInternal(uint256 amountOut,address recipient,uint160 sqrtPriceLimitX96,SwapCallbackData memory data
) private returns (uint256 amountIn) {// allow swapping to the router address with address 0if (recipient == address(0)) recipient = address(this);//从路径中解码出第一个池子(address tokenOut, address tokenIn, uint24 fee) = data.path.decodeFirstPool();//是否token0兑换token1bool zeroForOne = tokenIn < tokenOut;//调用底层池子的swap函数执行交易(int256 amount0Delta, int256 amount1Delta) =getPool(tokenIn, tokenOut, fee).swap(recipient,zeroForOne,-amountOut.toInt256(), //指定输出需转为负数sqrtPriceLimitX96 == 0? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1): sqrtPriceLimitX96,abi.encode(data));//确定amountIn和amountOutuint256 amountOutReceived;(amountIn, amountOutReceived) = zeroForOne? (uint256(amount0Delta), uint256(-amount1Delta)): (uint256(amount1Delta), uint256(-amount0Delta));// it's technically possible to not receive the full output amount,// so if no price limit has been specified, require this possibility awayif (sqrtPriceLimitX96 == 0) require(amountOutReceived == amountOut);
}

可见和 exactInputInternal 的实现也是大同小异。不过,有一个细节需要补充一下。因为是指定的输出数额,所以调用底层的 swap 函数时,第三个传参转为了负数,这也是前面讲解 UniswapV3Pool 的 swap 函数时讲过的,当指定的交易数额是输出的数额时,则需传负数。

exactInputInternal 一样,在当前函数里没有支付 token 的逻辑,也是统一在 uniswapV3SwapCallback 回调函数里去完成支付。

exactInput

exactInput 函数则用于处理跨多个池子的指定输入数量的交易,相比单池交易会复杂一些,而且这里面的逻辑还有点绕,我们来进行一一剖析。其实现代码如下:

struct ExactInputParams {bytes path;         //交易路径address recipient;  //收款地址uint256 deadline;   //过期时间uint256 amountIn;   //指定输入token数量uint256 amountOutMinimum; //输出token的最小数量
}function exactInput(ExactInputParams memory params)externalpayableoverridecheckDeadline(params.deadline)returns (uint256 amountOut)
{//调用者需支付路径中的第一个代币address payer = msg.sender;//遍历路径while (true) {//路径中是否还存在多个池子bool hasMultiplePools = params.path.hasMultiplePools();//先前交换的输出成为后续交换的输入params.amountIn = exactInputInternal(params.amountIn,hasMultiplePools ? address(this) : params.recipient,0,SwapCallbackData({path: params.path.getFirstPool(), // 只需要路径里的第一个池子payer: payer}));//当路径依然由多个池子组成时,则继续循环,否则退出循环if (hasMultiplePools) {payer = address(this);//跳过第一个token,作为下一轮的路径params.path = params.path.skipToken();} else {//最后一次兑换,把前面设为了amountIn的重新赋值给amountOutamountOut = params.amountIn;break;}}require(amountOut >= params.amountOutMinimum, 'Too little received');
}

其中,需要跨多个池子的路径编码方式如下图:

uniswapV3-path.webp

和 UniswapV2 一样,这个路径是由前端计算出来再传给合约的。寻找最优路径的算法也是和 UniswapV2 一样的思路。

exactInput 函数的核心实现逻辑是,循环处理路径中的每一个配对池,每处理完一个池子的交易,就从路径中移除第一个 token 和 fee,直到路径只剩下最后一个池子就结束循环。期间,每一次执行 exactInputInternal 后,将返回的 amounOut 作为下一轮的 amountIn。第一轮兑换时,payer 是合约的调用者,即 msg.sender,而输出代币的 recipient 则是当前合约地址。中间的每一次兑换,payerrecipient 都是当前合约地址。到最后一次兑换时,recipient 才转为用户传入的地址。

exactOutput

剩下最后一个函数 exactOutput 了,也是用于处理跨多个池子的的交易,而指定的是输出的数量。以下是其代码实现:

struct ExactOutputParams {bytes path;        //交易路径address recipient; //收款地址uint256 deadline;  //过期时间uint256 amountOut; //指定输出token数量uint256 amountInMaximum; //输入token的最大数量
}function exactOutput(ExactOutputParams calldata params)externalpayableoverridecheckDeadline(params.deadline)returns (uint256 amountIn)
{// it's okay that the payer is fixed to msg.sender here, as they're only paying for the "final" exact output// swap, which happens first, and subsequent swaps are paid for within nested callback framesexactOutputInternal(params.amountOut,params.recipient,0,SwapCallbackData({path: params.path, payer: msg.sender}));amountIn = amountInCached;require(amountIn <= params.amountInMaximum, 'Too much requested');amountInCached = DEFAULT_AMOUNT_IN_CACHED;
}

可看到其逻辑就直接调用内部函数 exactOutputInternal 完成交易,并没有像 exactInput 一样的循环处理。但在整个流程中,其实还是进行了遍历路径的多次交易的,只是这个流程完成得比较隐晦。其关键其实是在 uniswapV3SwapCallback 回调函数里,后面我们会说到。

uniswapV3SwapCallback

以下就是回调函数的实现:

function uniswapV3SwapCallback(int256 amount0Delta,int256 amount1Delta,bytes calldata _data
) external override {require(amount0Delta > 0 || amount1Delta > 0);//解码出_data数据SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData));//解码出路径的第一个池子(address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool();//校验callback的调用者CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee);//用于判断当前需要支付的代币(bool isExactInput, uint256 amountToPay) =amount0Delta > 0? (tokenIn < tokenOut, uint256(amount0Delta)): (tokenOut < tokenIn, uint256(amount1Delta));if (isExactInput) { //指定金额的是输入,直接执行支付pay(tokenIn, data.payer, msg.sender, amountToPay);} else { //指定金额的是输出// either initiate the next swap or payif (data.path.hasMultiplePools()) {// 路径里有多个池子时,则跳过路径的第一个token,使用下一个配对的池子进行交易data.path = data.path.skipToken();exactOutputInternal(amountToPay, msg.sender, 0, data);} else { //只剩下一个池子,执行支付amountInCached = amountToPay;tokenIn = tokenOut; // swap in/out because exact output swaps are reversedpay(tokenIn, data.payer, msg.sender, amountToPay);}}
}

另外,这个是 swap 时的回调函数。而之前的文章我们还讲了另一个回调函数 uniswapV3MintCallback 是添加流动性时的回调函数,两者是不同的,不要搞混了。

其逻辑实现并不复杂。首先,先把 _data 解码成 SwapCallbackData 结构体类型数据。接着,解码出路径的第一个池子。然后,通过 verifyCallback 校验调用当前回调函数的是否为底层 pool 合约,非底层 pool 合约是不允许调起回调函数的。

isExactInputamountToPay 的赋值需要拆解一下才好理解。首先需知道,amount0Deltaamount1Delta 其实是一正一负的,正数是输入的,负数是输出的。因此,amount0Delta 大于 0 的话则 amountToPay 就是 amount0Delta,否则就是 amount1Delta 了。 amount0Delta 大于 0 也说明了输入的是 token0,因此,当 tokenIn < tokenOut 的时候,说明 tokenIn 就是 token0,也即是说用户指定的是输入数量,所以这时候的 isExactInput 即为 true

当指定金额为输出的时候,也就是处理 exactOutputexactOutputSingle 函数的时候。我们前面看到 exactOutput 的代码逻辑里并没有对路径进行遍历处理,这个遍历其实就是在这个回调函数里完成的。仔细看这段代码:

if (data.path.hasMultiplePools()) {// 路径里有多个池子时,则跳过路径的第一个token,使用下一个配对的池子进行交易data.path = data.path.skipToken();exactOutputInternal(amountToPay, msg.sender, 0, data);
}

这不就是遍历路径多次执行 exactOutputInternal 了吗。

至此,SwapRouter 合约也讲解完了。

http://www.mnyf.cn/news/45734.html

相关文章:

  • h5页面用什么做网站建设推广优化
  • 广州市网站建设价格seo扣费系统
  • 上海网站制作培训班做网站怎么赚钱
  • 美国主机教育网站建设嘉峪关seo
  • 个人兼职做网站长沙网站制作费用
  • 哪个网站美丽乡村做的比较好长沙网站公司品牌
  • 网站建设专题seo网站优化案例
  • 对做网站有什么建议杭州网站优化多少钱
  • 网站不能自行备案吗湖南网站seo推广
  • 未来做那些网站能致富培训网址
  • 苏州有什么好玩的湖南seo推广系统
  • 网站建设维护是干什么广州百度推广电话
  • 做设计在哪个网站找图片seo课程培训机构
  • 国外免费psd网站信息流广告接单平台
  • 做网站的数据库的选择手机网站关键词快速排名
  • 电商网站 建设步骤手机seo排名软件
  • 寻花问柳一家专门做男人的网站seo研究
  • 网站建设试题南京seo优化公司
  • 国外有什么网站是做服装的推广网站排名优化seo教程
  • 品牌网站建设内容成人企业管理培训课程
  • 建设网站的获客渠道c++培训班学费一般多少
  • 在线代理浏览网站免费写文章免费的软件
  • 哪个云服务器便宜又好南京seo网络推广
  • 管理系统的设计与实现廊坊百度提升优化
  • 唯品会是哪做的网站百度识图扫一扫入口
  • 医疗器械网站备案前置审批事例知识搜索引擎
  • 网站建设 有限公司网络广告策划的步骤
  • 门户网站内容旺道seo推广有用吗
  • 网站建设undefined武汉网站设计十年乐云seo
  • 机关网站建设建议百度小程序对网站seo