【Solidity】关于gas费优化与示例

关于gas费优化问题

首先我们先来看一下这段代码

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasGolf{

    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] memory nums) external{
        for(uint i = 0;i<nums.length;i+=1){
            bool isEven = nums[i] % 2 == 0;
            bool isLessThan99 = nums[i] < 99;
            if(isEven && isLessThan99){
                total += nums[i];
            }
        }
    }
}

在这段代码中定义了一个total的状态变量,并且将其传入数组的偶数以及小于99的数去进行累加。

当我们一开始运行sum方法的时候,我们可以看出第一次执行的gas费用为

28654

现在我们开始第一个优化:将memory改为calldata

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        for(uint i = 0;i<nums.length;i+=1){
            bool isEven = nums[i] % 2 == 0;
            bool isLessThan99 = nums[i] < 99;
            if(isEven && isLessThan99){
                total += nums[i];
            }
        }
    }
}

此时我们再看看所需要用到的gas费用,此时少了2000左右

26909

为什么会这样呢?

我们对比一下calldata和memory的区别

在Solidity中,calldata是指函数调用参数的存储位置。而memory是在函数内部声明的临时变量的存储位置。

使用calldatamemory更能节省gas费用的原因是,calldata是只读的,在函数执行期间不能被修改。相反,memory中的数据可以在函数执行期间被修改,这意味着如果你使用memory存储大数据结构,每次修改都需要消耗更多的gas费用。

在这个场景中我们可以看到似乎nums作为一个入参似乎并不需要被改变,所以这里我们直接用calldata即可。

我们再来看看循环体内部

我们发现这里面的total这个状态变量,在每一次循环里面都会将total里面的重新赋值,这种在整体状态变量层面的操作实际上是非常浪费gas的。

我们不妨换一种思路,将total抽离到循环外,方法内单独设置一个变量去将total拷贝到内存中去,也就是我们每次累加的是内存中的一个变量,这样并不会写入状态变量。最后再循环结束后再一次性的将结果写入到状态变量中。

代码如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        for(uint i = 0;i<nums.length;i+=1){
            bool isEven = nums[i] % 2 == 0;
            bool isLessThan99 = nums[i] < 99;
            if(isEven && isLessThan99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时我们可以执行看看gas费用的消耗,此时又节省了200左右

26698

我们再观察一下里面还有什么地方可以优化的

不难看出里面其实isEven和isLessThan99其实没必要单独给他每一次都赋值,我们直接给他合并到条件判断里面就好,合并后的效果如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        for(uint i = 0;i<nums.length;i+=1){
            if(nums[i] % 2 == 0 && nums[i] < 99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时gas费用又节省了300左右

26380

我们再看看for(uint i = 0;i<nums.length;i+=1)这一段代码,i+=1实际上会将 i 的值复制到一个临时变量中,然后对 i 进行递增操作,最后再将临时变量的值返回给表达式。那有没有可以避免创建临时变量的方法呢?答案是有的:我们只需要改为++i即可

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        for(uint i = 0;i<nums.length;++i){
            if(nums[i] % 2 == 0 && nums[i] < 99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时的gas费用又节省了300多

26008

同样是刚才的循环,我们可以看出每一次循环中都要读取出数组的长度,这样每次循环都要执行这个方法,那么既然他的长度是不变的,我们不如直接给他存入缓存变量中。代码如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        uint len = nums.length;
        for(uint i = 0;i<len;++i){
            if(nums[i] % 2 == 0 && nums[i] < 99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时gas费用又省了100多

25973

再看看循环内部的代码,我们针对循环体内数组的元素nums[i]其实可以提前复制到内存中,代码如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        uint len = nums.length;
        for(uint i = 0;i<len;++i){
            uint num = nums[i];
            if(num % 2 == 0 && num < 99){
                _total += num;
            }
        }
        total = _total;
    }
}

此时gas费用又节省了200左右

25811
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是一个比较省gas的ERC721合约示例代码: ``` pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts/utils/Address.sol"; contract MyNFT is IERC721 { using Address for address; mapping (uint256 => address) private _owners; mapping (address => uint256) private _balances; mapping (uint256 => address) private _tokenApprovals; mapping (address => mapping (address => bool)) private _operatorApprovals; function balanceOf(address owner) public view override returns (uint256) { require(owner != address(0), "ERC721: balance query for the zero address"); return _balances[owner]; } function ownerOf(uint256 tokenId) public view override returns (address) { address owner = _owners[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } function safeTransferFrom(address from, address to, uint256 tokenId) public override { safeTransferFrom(from, to, tokenId, ""); } function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _safeTransfer(from, to, tokenId, _data); } function transferFrom(address from, address to, uint256 tokenId) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transfer(from, to, tokenId); } function approve(address to, uint256 tokenId) public override { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _tokenApprovals[tokenId] = to; emit Approval(owner, to, tokenId); } function setApprovalForAll(address operator, bool approved) public override { require(operator != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function isApprovedForAll(address owner, address operator) public view override returns (bool) { return _operatorApprovals[owner][operator]; } function getApproved(uint256 tokenId) public view override returns (address) { require(_owners[tokenId] != address(0), "ERC721: approved query for nonexistent token"); return _tokenApprovals[tokenId]; } function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual { _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } function _transfer(address from, address to, uint256 tokenId) internal virtual { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); _owners[tokenId] = to; _balances[from] -= 1; _balances[to] += 1; emit Transfer(from, to, tokenId); } function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { require(_owners[tokenId] != address(0), "ERC721: operator query for nonexistent token"); address owner = ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) private returns (bool) { if (!to.isContract()) { return true; } bytes memory returndata = to.functionCall(abi.encodeWithSelector( IERC721Receiver(to).onERC721Received.selector, msg.sender, from, tokenId, _data ), "ERC721: transfer to non ERC721Receiver implementer"); bytes4 retval = abi.decode(returndata, (bytes4)); return (retval == _ERC721_RECEIVED); } bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; } ``` 这个合约代码只包含了ERC721标准规定的基本函数,没有额外的复杂逻辑,因此省去了一些不必要的计算和存储操作,从而降低了gas用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

诗竹白芍

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值