我们知道,在UniswapV2交易所中,人们很容易计算出一种代币兑换另一种代币的数量,因为我们只要有交易对的存量和交易手续费就能计算出了。而在UniswapV3 交易所中,虽然交易对保存了两种代币之间的相互价格,但是由于滑点的存在,无法直接计算用户可以兑换的另一种代币的数量。为此,UniswapV3有一个辅助合约Quoter.sol,它介绍了一种模拟交易的方法来获取用户可兑换的数量。本文正是基于这一模拟交易来得到获取代币在买卖和转移时的燃烧率的方法。
一、原理
本文基于市场上应用的最多的UniswapV2 AMM交易所。
不管是UniswapV2还是UniswapV3,都是先借后还系统,也就是大名鼎鼎的闪电贷。既然我们可以通过模拟交易获取用户可以兑换的数量,那么我们和真实的余额比较一下(此时已经借给我们了),如果两者有差额,那么这个差额就是燃烧的代币数量了。差额除以借的数量,就可以计算出其交易或者转移燃烧率了。
二、实现合约
实现合约当前仅基于SafeMoon类代币,也就是固定损耗,不管买卖和转移都是一样的,例如10%。
我们基于UniswapV3的Quoter.sol合约写一个基于UniswapV2版的服务于我们特殊目的的合约CustomQuoter.sol 。
pragma solidity =0.6.6;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import "./interfaces/IERC20Interface.sol";
contract CustomQuoter {
function quoteBuyRate(address pair, address token) external returns (uint buyRate) {
address token0 = IUniswapV2Pair(pair).token0();
(uint reserve00, uint reserve01, ) = IUniswapV2Pair(pair).getReserves();
uint amountOut0 = token0 == token ? reserve00 / 10 : 0;
uint amountOut1 = token0 == token ? 0 : reserve01 /10;
try
IUniswapV2Pair(pair).swap(amountOut0,amountOut1,address(this),bytes("amm"))
{} catch (bytes memory reason) {
return parseRevertReason(reason);
}
}
function parseRevertReason(bytes memory reason) private pure returns (uint256) {
if (reason.length != 32) {
if (reason.length < 68) revert('Unexpected error');
assembly {
reason := add(reason, 0x04)
}
revert(abi.decode(reason, (string)));
}
return abi.decode(reason, (uint256));
}
fallback () external payable virtual {
bytes memory data = msg.data;
uint amount0;
uint amount1;
assembly {
amount0 := mload(add(data,68))
amount1 := mload(add(data,100))
}
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
address token = amount0 > 0 ? token0 : token1;
uint amount = amount0 > 0 ? amount0 : amount1;
uint bal = IERC20Interface(token).balanceOf(address(this));
require(amount >= bal,"inner error");
uint rate = 1000 * (amount - bal) / amount;
assembly {
let ptr := mload(0x40)
mstore(ptr, rate)
revert(ptr, 32)
}
}
}
其中parseRevertReason 函数是直接照搬的UniswapV3的Quoter.sol 的。 由于除法截断的原因,我们将燃烧率放大了10倍。最后得到的burnRate 除以10再上取整就是SafeMoon的燃烧率了。
另外,这里和交易对中另一种代币没有关系 ,任意代币任意数量均可都行。对交易对里查询代币的数量也没有要求。
三、优点
本合约的优点是只要有查询代币的交易对,不管是和什么代币组成的交易对,也不管是什么交易所,Sushi也好,Uniswap也好都行,也不需要知道交易手续费,同时对交易对里两种代币的数量也没有要求。 只要交易对是标准的UniswapV2类型并支持闪电贷(当前在不同的链上有成百上千个这样的AMM交易所),就可以进行查询。
四、合约调用
至于怎么调用该合约,大家一个方法是可以看一下UniswapV3的前端代码,这个模拟交易的过程,文章 Uniswap v3 详解(三):交易过程 中有详细的介绍,并且文章中给出了相关前端代码链接。
另一个调用该合约的方法是看一下我相关的CSDN上的区块链进阶文章,聪明的读者是可以总结出来该方法的。
PS. 调用合约的quoteBuyRate 方法是不需要花钱的哟。
五、未完成的功能
这里的这个合约适用范围有限,代币只能是SafeMoon类在转移时有损耗的代币。有的特定代币指定了买卖池,只有在买卖的时候有损耗,在交易的时候没有损耗或者损耗不同。如果是这种特定代币,我们的合约也是适用的。不过当前我们这个合约只是得到了特定代币买入时的损耗。其卖出时和转移时的损耗需要稍微添加一些代码,这里我就留下个尾巴,有兴趣的小伙伴可以自己动手完成一下。。
|