Solidity Uniswap V2 优化

uint112        

        那个奇怪的 uint112 类型是什么?为什么不用 uint256?答案是:gas优化。

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

        EVM 的每个操作都会消耗一定量的gas。简单的运算,如算术运算,消耗的gas很少,但有些运算消耗的gas很多。最耗gas的操作是 SSTORE,将数值保存到合约存储区。与之对应的 SLOAD 也很昂贵。因此,如果智能合约开发者能尽量优化合约的耗gas量,对用户是有好处的。使用 uuint112 作为储备变量正是为了实现这一目的。

        看看我们是如何布置变量的:

address public token0;

address public token1;



uint112 private reserve0;

uint112 private reserve1;

uint32 private blockTimestampLast;



uint256 public price0CumulativeLast;

uint256 public price1CumulativeLast;

        必须完全按照这个顺序排列。原因是每个状态变量对应一个特定的存储槽,而 EVM 使用 32 字节的存储槽(每个存储槽正好是 32 字节)。当你读取一个状态变量的值时,它会从该变量所链接的存储槽中读取。每个 SLOAD 调用一次读取 32 字节,每个 SSTORE 调用一次写入 32 字节。由于这些操作都很昂贵,所以我们非常希望减少存储读取和写入的次数。而这正是对状态变量进行适当布局可能会有所帮助的地方。

        如果有几个连续的状态变量占用的时间少于 32 字节,该怎么办?我们需要分别读取它们吗?事实证明,不需要。EMV 对小于 32 字节的邻接变量进行了打包。

        再来看看我们的状态变量:

        1、前两个是地址变量。地址需要 20 个字节,而两个地址需要 40 个字节,这意味着它们必须使用不同的存储槽。它们不能存储在一个槽中,因为根本放不下。

        2、两个 uint112 变量和一个 uint32 变量,这看起来很有趣: 112+112+32=256! 这意味着它们可以放在一个存储槽中!这就是选择 uint112 作为储备变量的原因:储备变量总是一起读取的,最好从存储空间一次性加载,而不是分开加载。这样可以节省一次 SLOAD 操作,而由于储备变量使用频率很高,这就大大节省了油耗。

        3、两个 uint256 变量。这两个变量不能打包,因为每个变量都要占用一个完整的槽。

        同样重要的是,两个 uint112 变量必须位于占用一个满槽的变量之后,这样才能确保第一个变量不会被打包到前一个槽中。

整数溢出

        我们为什么将累计价格计算放到unchecked中?

        智能合约的另一个常见漏洞是整数溢出或下溢。uint256 整数的最大值是

        最小值最小为0;整数溢出是指增加整数变量的值,使其大于最大值,这将导致溢出:该值从0,重新开始计数。例如:

        同样,从0中减去一个数字会得到一个非常大的数字,例如:

        在 0.8.0 版之前,Solidity 无法检查溢出和溢出不足,因此开发人员开发了一个库: SafeMath 库。如今,这个库已不再需要,因为 Solidity 现在会在检测到溢出或下溢时抛出异常。

        Solidity 0.8.0 还引入了unchecked block,顾名思义,在它内不禁止溢出/下溢检测。在计算 timeElapsed 和累积价格时,我们使用了unchecked block。这似乎不利于合约的安全性,但时间戳和累计价格溢出是意料之中的:溢出时不会发生任何坏事。我们希望它们溢出时不会出错,这样它们才能正常运行。

        这种情况很少发生,因此溢出/溢出检测功能几乎不应该被禁用。

安全发送

你可能已经注意到了我们使用的发送代币的函数很奇怪:

function _safeTransfer(

  address token,

  address to,

  uint256 value

) private {

  (bool success, bytes memory data) = token.call(

    abi.encodeWithSignature("transfer(address,uint256)", to, value)

  );

  if (!success || (data.length != 0 && !abi.decode(data, (bool))))

    revert TransferFailed();

}

为什么不直接在 ERC20 接口上调用传输方法?

        在pair合约中,当进行代币转账时,我们总是希望确保转账成功。根据 ERC20,转账方法必须返回一个布尔值:成功则返回 true,失败则返回 fails。大多数代币都能正确实现这一点,但也有一些代币不能,它们什么也不返回。当然,我们无法检查代币合约的执行情况,也无法确定代币是否真的进行了转移,但我们至少可以检查转移结果。如果转移失败,我们也不想继续。

        这里的调用是address的一个方法,这是一个底层函数,可以让我们对合约调用进行更精细的控制。在这种特殊情况下,无论转移方法是否返回结果,它都能让我们获得转移结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

0xweb3q

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

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

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

打赏作者

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

抵扣说明:

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

余额充值