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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0xweb3q

有钱的捧个钱场,没钱的捧个人场

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值