Gas_optimization

- 10 mins

原文: https://hackmd.io/@totomanov/gas-optimization-loops

总结:

大于等于和小于等于如何使用比较省gas

function ddd()external view{//23312
    require(ccc >= 0);
}
function eee()external view{//23363
    require(ccc > 0);
}
function bbb()external view{//23519
    require(ccc+1 > 0);
}

结论:上个版本可能有这个区别,但是这个版本好像没有这个区别了

附上另一个测试

contract AAA{

    function loop_lte() external pure returns (uint256 sum) {//58308
        for(uint256 n = 0; n <= 99; n++) {
            sum += n;
        }
    }

    function loop_lt() external pure returns (uint256 sum) {//58633
        for(uint256 n = 0; n < 100; n++) {
            sum += n;
        }
    }

}

for循环怎么写比较节省gas

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract AAA{

    function loop_unchecked_plusplus() external pure returns (uint256 sum) {//46633
        for(uint256 n = 0; n < 100;) {
            sum += n;
            unchecked {
                n++;
            }
        }
    }

    function loop_checked_plusplus() external pure returns (uint256 sum) {//58611
        for(uint256 n = 0; n < 100;n++) {
            sum += n; 
        }
    }

    function loop_assembly() external pure returns (uint256) {//27752
        assembly {
            let sum := 0
            for {let n := 0} lt(n, 100) {n := add(n, 1)} {
                sum := add(sum, n)
            }
            mstore(0, sum)
            return(0, 32)
        }
    }

}

for循环中,储存length,节省gas

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract AAA{

    function loopArray(uint256[] calldata ns) external pure returns (uint256 sum) {//26534
        for(uint256 i = 0; i < ns.length;) {
            sum += ns[i];
            unchecked {
                i++;
            }
        }
    }

    function loopArray_cached(uint256[] calldata ns) external pure returns (uint256 sum) {//26425
        uint256 length = ns.length;
        for(uint256 i = 0; i < length;) {
            sum += ns[i];
            unchecked {
                i++;
            }
        }
    }

    function loopArray_assembly(uint256[] calldata ns) external pure returns(uint256 sum) {//24218
        assembly {
            let guard := add(1, calldatasize())
            for {let offset := ns.offset} 
                lt(offset, guard) 
                {offset := add(offset, 32)} 

                {sum := add(sum, calldataload(offset))}
        }
    }

}
    

批量转账的时候

可以用yul把函数选择器存入内存

把数组转成数字

原来数组[1, 6, 13, 22, 24, 25, 30, 33, 94, 215]
假设每个数字都是8个字节, 那么最大也就是2^8=255
那么我们可以把这串数组转换为一个uint256
每个数字是两个16hex
转换完成后0x00000000000000000000000000000000000000000000d75e211e1918160d0601
从右开始读01,06,0d,16,…,d7. 

原来的函数

function loopArray_cached(uint256[] calldata ns) public returns (uint256 sum) {
    uint256 length = ns.length;
    for(uint256 i = 0; i < length;) {
        sum += ns[i];
        unchecked {
            i++;
        }
    }
}
function loop_arr_magic(uint256 encoded_ns, uint256 length) public returns (uint256 sum) {
    for(uint256 i = 0; i < length;) {
        sum += encoded_ns & 0xff;
        encoded_ns >>= 8; // mask移动8, 8 = 2个字节
        unchecked {
            i++;
        }
    }
}

把数组转成数字实战

比如gmx里面有一个需求, 每隔几秒钟, 就把链下的交易标记价格更新到链上, 本来的实现方式

function setPrices(address[] memory _tokens, uint256[] memory _prices) external onlyAdmin {
    for (uint256 i = 0; i < _tokens.length; i++) {
        address token = _tokens[i];
        prices[token] = _prices[i];
        if (fastPriceEvents != address(0)) {
          IFastPriceEvents(fastPriceEvents).emitPriceEvent(token, _prices[i]);
        }
    }
    lastUpdatedAt = block.timestamp;
}

after

我们修改了传参方式和数据存储, 大幅的降低了gas(note: 16进制中, 我们吧uint256要移动4位, 才是移动16进制1位, 因为移动是2进制的算法, 2^4=16哦!)

contract FastPriceFeed{ 
    // 我们这里12个hex算作一组数据, 64个hex一共一次可以传4个资产的价格数据, 由于是美元计价, 我们设计三个参数
    // 1. MUL表示数字是否要乘以/除以一堆0
    // 2. 0的个数
    // 3. 10个代表价格的数字
    uint256 constant BTC_MUL_MASK      = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0; // prettier-ignore
    uint256 constant BTC_MUL_START_BIT_POSITION = 0;

    uint256 constant BTC_DECIMALS_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00F; // prettier-ignore
    uint256 constant BTC_DECIMALS_START_BIT_POSITION = 4;
    uint256 constant BTC_PRICE_MASK    = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000FFF; // prettier-ignore
    uint256 constant BTC_PRICE_START_BIT_POSITION = 12; // 这个12是因为3个hex = 3*4=12

    uint256 constant ETH_MUL_MASK      = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FFFFFFFFFFFF; // prettier-ignore
    uint256 constant ETH_MUL_START_BIT_POSITION = 0 + 12*4;

    uint256 constant ETH_DECIMALS_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFF; // prettier-ignore
    uint256 constant ETH_DECIMALS_START_BIT_POSITION = 4 + 12*4;

    uint256 constant ETH_PRICE_MASK    = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000FFFFFFFFFFFFFFF; // prettier-ignore
    uint256 constant ETH_PRICE_START_BIT_POSITION = 12 + 12*4; 

    uint256 public constant PRICE_PRECISION = 10 ** 6; // 我们保留美元的6位小数,足够了

    uint256 public priceMap;

    function setPricesMap1(uint256 _map1) external{
        priceMap = _map1;
    }

    function setBTCPrice(uint256 _price)external {
        priceMap = (priceMap & BTC_PRICE_MASK) | (_price << BTC_PRICE_START_BIT_POSITION);
    } 

    function setBTCDecimals(uint256 _decimals)external {
        priceMap = (priceMap & BTC_DECIMALS_MASK) | (_decimals << BTC_DECIMALS_START_BIT_POSITION);
    } 

    function setBTCMul(bool _mul)external {
        priceMap = (priceMap & BTC_MUL_MASK) | (uint256(_mul ? 1 : 0) << BTC_MUL_START_BIT_POSITION);
    }

    function getBTCPrice()public view returns(uint256){
        return (priceMap & ~BTC_PRICE_MASK) >> BTC_PRICE_START_BIT_POSITION;
    }

    function getBTCDecimals()public view returns(uint256){
        return (priceMap & ~BTC_DECIMALS_MASK) >> BTC_DECIMALS_START_BIT_POSITION;
    }

    function getBTCMul()public view returns(bool){
        return ((priceMap & ~BTC_MUL_MASK) >> BTC_MUL_START_BIT_POSITION) & 1 != 0;
    } 
    
    function setETHPrice(uint256 _price)external {
        priceMap = (priceMap & ETH_PRICE_MASK) | (_price << ETH_PRICE_START_BIT_POSITION);
    } 

    function setETHDecimals(uint256 _decimals)external {
        priceMap = (priceMap & ETH_DECIMALS_MASK) | (_decimals << ETH_DECIMALS_START_BIT_POSITION);
    } 

    function setETHMul(bool _mul)external {
        priceMap = (priceMap & ETH_MUL_MASK) | (uint256(_mul ? 1 : 0) << ETH_MUL_START_BIT_POSITION);
    }

    function getETHPrice()public view returns(uint256){
        return (priceMap & ~ETH_PRICE_MASK) >> ETH_PRICE_START_BIT_POSITION;
    }

    function getETHDecimals()public view returns(uint256){
        return (priceMap & ~ETH_DECIMALS_MASK) >> ETH_DECIMALS_START_BIT_POSITION;
    }

    function getETHMul()public view returns(bool){
        return ((priceMap & ~ETH_MUL_MASK) >> ETH_MUL_START_BIT_POSITION) & 1 != 0;
    } 
    
    function getBTCPriceWithDecimals()external view returns(uint256){
        if(getBTCMul()) {
            return getBTCPrice() * (10 ** getBTCDecimals()) * PRICE_PRECISION;
        } else {
            return getBTCPrice() / (10 ** getBTCDecimals())*  PRICE_PRECISION;
        }
    } 
}