您正在查看: Ethereum-优秀转载 分类下的文章

Solidity优化-减少合约gas消耗

背景

在以太坊系公链中,合约部署和调用是需要发送交易并消耗 gas 的,而 gas 的使用量决定了该笔交易的费用。因此,设计省钱的合约是很重要的。
在部署合约时,我们希望减小合约编译后的字节码大小,来减少合约部署时的 gas 消耗。而好的代码实现,能够减少合约调用时的 gas 消耗。

减少 gas 消耗的方法

以下介绍一些减少合约 gas 消耗的具体方法。

1、编译合约时使用优化器

使用编译器 solc 编译合约时启动优化器 optimizer,它将简化复杂的表达方式,能减小编译后的合约字节码大小,从而减少部署时的 gas 消耗,同时也能减少合约调用时的消耗。
基于 opcode 的优化器会执行一系列的简化规则,把重复的代码合并,把多余的代码删除。目前(v0.8.11 版本),--optimize 参数激活的是基于 opcode 的优化器。

solc --optimize --optimize-runs 200

运行次数 --optimize-runs 指定了部署的代码的每个操作码在合同的生命周期内被执行的大致频率。这意味着它是代码大小(部署成本)和代码执行成本(部署后的成本)之间的一个折衷参数。次数越小,编译出的字节码越小,但是调用该合约函数可能需要更多 gas。
此外还有基于 Yul 的优化器,更加强大,因为它可以跨函数调用工作。
详情见文档

2、SSTORE 指令

在链上存储变量的值,要用到 SSTORE 指令。在不同情况下,该指令消耗的 gas 不同。
https://eips.ethereum.org/EIPS/eip-1087
我们使用如下合约代码部署在 QEasyWeb3测试链上进行测试:

pragma solidity ^0.8.0;

contract Test {
    uint256 x;

    function emptySet(uint256 _x) public {}

    function set(uint256 _x) public {
        x = _x;
    }
}

实际测试结果如下:

  • 将 x 变量从零值设置为非零值 1,消耗 41406 gas,其中交易基础 gas 21000,推算存储实际消耗 20000 gas。
  • 将 x 变量从非零值设置为零值,消耗 13197 gas,其中交易基础 gas 21000,推算存储实际返还约 8000 gas。
  • 将 x 变量从非零值 1 设置为另一个非零值 10000,消耗 26418 gas,推算存储实际消耗 5000 gas。
  • 将 x 变量从非零值设置为同一个非零值,消耗 22218 gas,推算存储实际消耗约 800 gas。
  • 将 x 变量从零值设置为零值,消耗 22194 gas,推算存储实际消耗约 800 gas。

这里扩展一点,合约自毁和将变成从非零值设置为零值一样,是会返回 gas 的。

3、变量存储原则

从 SSTORE 指令的 gas 消耗测试结果可以看到,存储操作是非常消耗 gas 的,特别是将变量从零值设置为非零值时。

因此,我们应该考虑:

  • 避免在链上存储用不上、不重要的数据,如介绍、描述信息等。
  • 考虑使用事件来存储,要比将它们存储在变量中便宜得多。
  • 在 IPFS 上存储较大的数据,如图片、文档等,在合约中只储存其哈希值。
  • 无状态合约,即用交易数据和事件等来保存数据,而不是改变合约的存储状态。比如发送一个交易并传递你想要存储的值,而不是真正存储它。更多可见这篇文章

同样消耗 gas 比较多的操作指令还有 CREATE 、 CREATE2 等,我们使用时应该注意。

4、选择变量数据类型

不同数据类型的存储消耗不同。在满足业务的情况下,我们应该选择 gas 消耗更小的数据类型。
在没有办法将多个变量放入同一个插槽时(在【5、紧凑状态变量打包】中说明),尽量使用 256 位的变量,例如 uint256 和 bytes32。

在使用小于 32 字节的变量数据类型时,合约的 gas 使用量可能会高于使用 32 字节的类型。这是因为 EVM 每次操作 32 个字节, 所以如果变量大小比 32 字节小,EVM 必须执行额外的操作以便将 32 字节大小缩减到到所需的大小。

我们将如下两个合约部署在 QEasyWeb3测试链上:

// 消耗 gas 68820
contract A {
    uint8 x = 0;
}

// 消耗 gas 67900
contract A {
    uint256 x = 0;
}

发现存储一个 uint256 变量比 uint8 变量消耗的 gas 更少。
此外,在 EVM 执行计算也需要额外的操作,除 uint256 之外的其他 uint 类型在计算时需要耗费额外的 gas 进行转换。

尽量使用定长数组,通常它们更省 gas。比如使用定长字节数组 bytes1, bytes2 ... bytes32,而不是变长字节数组 bytes。如果要使用变长字节数组,则尽量使用 bytes 而不是 []byte,后者会更加浪费存储空间。详情可见文档

5、紧凑状态变量打包

首先,Solidity 合约数据存储的方案是为合约每个变量指定一个可计算的存储位置,数据存在容量为 2 ** 256 超级数组中,数组中每项数据的初始值为 0。

每个插槽可存储 256 位/32 字节数据:

合约状态变量存储结构相关描述可见文档

根据文档描述,静态大小的变量(除映射 mapping 和动态数组之外的所有类型)都从位置 0 开始连续放置在存储插槽(storage slot)中的。如果可能的话,存储大小少于 32 字节的多个变量会被打包到一个存储插槽中(每个存储插槽 256 位/32 字节),规则如下:

  • 存储插槽中的第一项会以低位对齐(即右对齐)的方式储存。
  • 值类型仅使用存储它们所需的字节数。
  • 如果存储插槽中的剩余空间不足以储存一个值类型变量,那么它会被移入下一个存储插槽。
  • 结构体和数组数据会使用一个新插槽进行存储,但结构体或数组中的各项,都会以这些规则进行打包。
  • 结构体和数组数据之后的变量会使用一个新插槽。

紧凑状态变量打包,就是将多个不需要用到 32 字节的值类型数据存储在一个插槽中。通过合理地排列状态变量的顺序、结构体的字段的顺序,使得尽可能多的状态变量打包到一个存储插槽中,最终使用更少的存储插槽,减少 gas 消耗。

注意,要在编译的时候使用优化器进行优化。

以定义一个结构体为例,我们将如下两个合约部署在QEasyWeb3测试链上:

contract Test {
    // 字段 a, b, c 分别使用了一个存储插槽,共使用 3 个
    struct A {
        uint a;
        uint b;
        uint c;
    }

    A a = A(10, 20, 30);
}

contract Test {
    // 字段 a, b 共需要 8 字节,可以共用一个存储插槽
    // 字段 c 需要 32 字节,前一个插槽不够放,因此开启使用一个新的存储插槽
    // 共使用 2 个存储插槽
    struct A {
        uint32 a;           // uint32 类型大小为 32 位/4 字节
        uint32 b;
        uint c;
    }

    A a = A(10, 20, 30);
}

// 第一个合约部署消耗 gas 127633,第二个合约部署消耗 gas 108833,减少 18800,近 20000 gas。

因为使用了紧凑变量打包,所以第二个合约少使用了一个存储插槽,减少了 gas 消耗。


因此我们可以考虑使用更小的 uint 子类型或者 bytes 子类型,通过合理地排序它们的位置,可以将存储空间最小化。
除结构体和数组数据外的其他变量,同样部署以下两个合约进行测试:

// 部署消耗 gas 129366
contract Test {
    uint128 x = 10;
    uint256 y = 10;
    uint128 z = 10;
}

// 部署消耗 gas 108674,少 20692 gas
contract Test {
    uint256 y = 10;
    uint128 x = 10;
    uint128 z = 10;
}

6、紧凑状态变量赋值

当我们使用紧凑状态变量打包时,多个变量被打包在一个存储插槽中,这时,同时读取和写入该插槽中的多个变量,多个读或写会合并为一个单一的操作,这样能够节省 gas。而如果你只是读或者写该插槽中的一个变量,效果可能相反,当一个变量的值被写入一个多变量存储插槽中时,存储槽必须先被读取,然后与新值结合,这样同一个插槽中的其他数据就不会被破坏。

在实际测试中,我们也发现,某些情况下统一插槽内变量的读或写没有优化合并为一个操作。
以下四种设置方式,我们设置 a 的值为 2,b 的值为 1,看实际的 gas 消耗。

contract structWrite {
  struct Object {
    uint64 v1;
    uint64 v2;
    uint64 v3;
    uint64 v4;
  }

  Object obj = Object(1, 1, 1, 1);

    // gas cost 33211
  function set1(uint64 a, uint64 b) public {
    obj.v1 = a + b;
    obj.v2 = a - b;
    obj.v3 = a * b;
    obj.v4 = a / (b + 1);
  }

    // gas cost 28411
  function set2(uint64 a, uint64 b) public {
    setObject(a + b, a - b, a * b, a / (b + 1));
  }

  function setObject(uint64 v1, uint64 v2, uint64 v3, uint64 v4) private {
    obj.v1 = v1;
    obj.v2 = v2;
    obj.v3 = v3;
    obj.v4 = v4;
  }

  // gas cost 28381
  function set3(uint64 a, uint64 b) public {
    uint64 v1 = a + b;
    uint64 v2 = a - b;
    uint64 v3 = a * b;
    uint64 v4 = a / (b + 1);
    obj.v1 = v1;
    obj.v2 = v2;
    obj.v3 = v3;
    obj.v4 = v4;
  }

    // gas cost 28613
  function set4(uint64 a, uint64 b) public {
    obj = Object(a + b, a - b, a * b, a / (b + 1));
  }

  // gas cost 22383
  function set5(uint64 a, uint64 b) public {
    uint64 v1 = a + b;
    uint64 v2 = a - b;
    uint64 v3 = a * b;
    uint64 v4 = a / (b + 1);
  }
}

实际我们看几个 set 方法编译出来的 opcode,发现 set1 使用了 4 个 SLOAD 和 4 个 SSTORE,而其他 set 方法只使用了 1 个 SSLOAD 和 1 个 SSTORE,编译器对其他写法进行了优化。

编译器将 4 个字段的读取和写入优化为一次操作,而第一种写法无法优化,因此要多消耗 5000 左右的 gas。因此,我们应该避免第一种写法。

7、内联汇编打包变量

编写内联汇编 (Inline Assembly) ,手动将多个变量堆叠在一起,打包到单个插槽中。

语法:使用 assembly{ ... } 来嵌入汇编代码段。

// 编码时将多个变量一起储存。
function encode(uint64 _a, uint64 _b, uint64 _c, uint64 _d) internal pure returns (bytes32 x) {
    assembly {
        let y := 0
        mstore(0x20, _d)
        mstore(0x18, _c)
        mstore(0x10, _b)
        mstore(0x8, _a)
        x := mload(0x20)
    }
}

function decode(bytes32 x) internal pure returns (uint64 a, uint64 b, uint64 c, uint64 d) {
    assembly {
        d := x
        mstore(0x18, x)
        a := mload(0)
        mstore(0x10, x)
        b := mload(0)
        mstore(0x8, x)
        c := mload(0)
    }
}

这种方式虽然节省了 gas,但是牺牲了代码的可读性,容易出错。

更多资料:Solidity Tutorial : all about Assembly

8、无需使用默认值初始化变量

无需使用默认值初始化变量。

// 部署消耗 gas 67054
contract Test {
    uint256 x;
}

// 部署消耗 gas 67912
contract Test {
    uint256 x = 0;
}

9、常量

在 solidity 中,声明为 constant 或者 immutable 的状态变量即常量。
constant 修饰的常量的值在编译时确定,而 immutable 修饰的常量的值在部署时确定。详情可见文档
尽量使用常量,常量是合约字节码的一部分,不占用存储插槽,使用常量比变量更省 gas。
在部署时,常量消耗的 gas 更少。

// 消耗 83681 gas,相比使用变量节省 20078 gas
contract A {
    uint256 public constant x = 1000;
}

// 消耗 90046 gas,相比使用变量节省 13713 gas
contract A {
    uint256 public immutable x = 1000;
}

// 消耗 103759 gas
contract A {
    uint256 public x = 1000;
}

在读取时,常量消耗的 gas 也更少。

contract A {
    uint256 public result;
    uint256 public constant x = 100;            // 调用 cal 方法消耗 41236 gas
    // uint256 public immutable x = 100;    // 调用 cal 方法消耗 41236 gas
    // uint256 public x = 100;                      // 调用 cal 方法消耗 42036 gas,读取存储变量消耗多消耗 800 gas,

    function cal() public {
        result = x;
    }

    // function cal() public {                      // 调用 cal 方法消耗 41236 gas
    //     result = 100;
    // }
}

10、函数修饰符

使用函数修饰符 view、pure。

  • 函数声明为 view,表示该函数不修改状态。
  • 函数声明为 pure,表示该函数不读取或修改状态。

详情可见文档

在以太坊中,如果不对状态进行修改,则可以发起一笔调用进行查询或其他操作,调用是不需要费用的。如果要对状态产生变更,则需发起一笔交易,交易是需要消耗 gas 和支付费用的。

在智能合约中,函数如果声明为 view 或者 pure ,则外部账户直接调用这些函数只需发起一次调用即可。如果不加这些修饰符,以太坊网络会把你的操作理解为一笔交易。

需要注意的是,如果在一笔交易中,某个未声明为 view 或者 pure 的合约函数的内部调用了声明为 view 或者 pure 的函数,还是需要消耗 gas 的。

部署下面的合约进行测试,其中 add 和 sub 方法都没有读取或修改状态,add 方法没有声明为 pure,则需要发起一笔交易并支付交易费用才能调用,而 sub 方法声明为 pure,则无需发起交易只发起调用即可,无需费用。

contract Test {

    function add(uint _x, uint _y) public returns (uint) {
        return _x + _y;
    }

    function sub(uint _x, uint _y) public pure returns (uint) {
        return _x - _y;
    }
}

11、避免重复修改状态变量

避免重复修改状态变量,比如在循环中重复修改状态变量的值。

contract Test {
    uint256 public count;

        // bad,消耗 gas 58582
    function set1() public {
        for (uint256 i = 0; i < 10; i++) {
            count++;
        }
    }

        // good,消耗 gas 24046
    function set2() public {
        uint256 temp;
        for (uint256 i = 0; i < 10; i++) {
            temp++;
        }
        count = temp;
    }
}

12、使用短路规则

操作符 || 和 && 适用常见的短路规则。
这意味着,假设f(x) 和 g(y) 返回 true 的概率一样,那么:

  • 在表达式 f(x) || g(y) 中,如果 f(x) 的计算结果为真,则不会执行 g(y)。因此应该将贵的方法放在后面。
  • 在表达式 f(x) && g(y) 中,如果 f(x) 的计算结果为假,则不会执行 g(y)。因此应该将贵的方法放在后面。

当然,实际情况是还需要考虑两个方法执行的失败概率,从而整体评估方法的排序。

13、布尔类型

在 solidity 中,布尔类型 bool 实际为 uint8,即使用 8 位的存储空间,每个存储插槽能装入 32 个布尔类型值。而布尔值只能有两个值:True 或 False,其实只需要在单个存储位中就可以保存布尔值。

在有非常多个布尔类型变量,或者是需要布尔类型的数组时,你可以考虑使用一个 uint256 变量,并使用其所有 256 位来表示各个布尔值。

要从 uint256 中获取单个布尔值,请使用以下函数:

function getBoolean(uint256 _packedBools, uint256 _boolNumber) public view returns(bool) {
    uint256 flag = (_packedBools >> _boolNumber) & uint256(1);
    return (flag == 1 ? true : false);
}

要设置或清除布尔值:

function setBoolean(
    uint256 _packedBools,
    uint256 _boolNumber,
    bool _value
) public view returns(uint256) {
    if (_value)
        return _packedBools | uint256(1) << _boolNumber;
    else
        return _packedBools & ~(uint256(1) << _boolNumber);
}

可以使用 BitMap 代替 mapping(uint256 => bool), 同样是使用了位操作处理。

14、默克尔树

使用默克尔树。在合约中保存一组数据的 merkleRoot,提供 verify 方法验证某条数据在这组数据中。相比使用一个 mapping 或数组来保存全部数据,减少了 gas 消耗。

以 ERC20 代币空投为例。参考 ENS 空投合约
核心代码:

bytes32 public merkleRoot;

function claimTokens(uint256 amount, address delegate, bytes32[] calldata merkleProof) external {
    bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
    (bool valid, uint256 index) = MerkleProof.verify(merkleProof, merkleRoot, leaf);
    require(valid, "ENS: Valid proof required.");
    require(!isClaimed(index), "ENS: Tokens already claimed.");

    claimed.set(index);
    emit Claim(msg.sender, amount);

    _delegate(msg.sender, delegate);
    _transfer(address(this), msg.sender, amount);
}

function verify(
    bytes32[] memory proof,
    bytes32 root,
    bytes32 leaf
) internal pure returns (bool, uint256) {
    bytes32 computedHash = leaf;
    uint256 index = 0;

    for (uint256 i = 0; i < proof.length; i++) {
        index *= 2;
        bytes32 proofElement = proof[i];

        if (computedHash <= proofElement) {
            // Hash(current computed hash + current element of the proof)
            computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
        } else {
            // Hash(current element of the proof + current computed hash)
            computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
            index += 1;
        }
    }

    // Check if the computed hash (root) is equal to the provided root
    return (computedHash == root, index);
}

同样可参考 Uniswap 空投使用的 merkle-distributor 以及 OneSwap 空投。

15、压缩交易输入数据

在函数参数较多的时候,我们可以压缩输入数据,类似紧凑状态变量打包,当有多个小于 32 字节大小的参数时,将多个参数打包为一个参数。
参考 Compress input in smart contract

16、调用外部合约

调用外部合约函数比调用内部函数消耗更多 gas。除非必要,否则不建议拆分多个合约,可以使用多个继承来管理和组织代码。
首先测试内部函数调用的 gas 消耗:

contract Math {
    function add(uint _x, uint _y) public pure returns (uint) {
        return _x + _y;
    }
}

contract Test is Math {
    uint sum;

        // 消耗 41710 gas
    function calculate(uint _x, uint _y) public {
        sum = add(_x, _y); 
    }
}

再测试外部函数调用的 gas 消耗,先部署被调用外部合约,得到合约地址:

contract Math {
    function add(uint _x, uint _y) public pure returns (uint) {
        return _x + _y;
    }
}

再部署调用合约,并测试:

contract Math {
    function add(uint _x, uint _y) public pure returns (uint) {
        return _x + _y;
    }
}

contract Test {
    uint sum;
    address constant MathContractAddr = 0x9549DfbBd66b3Cc078AD834C74b9EE1808Ef3AEB;

        // 消耗 43693 gas
    function calculate(uint _x, uint _y) public {
        sum = Math(MathContractAddr).add(_x, _y); 
    }
}

17、状态变量重复读取

多次读取状态变量,不会重复使用 SLOAD 指令,而是将值缓存起来。
我们部署下面两个合约进行测试:

contract Test {

    uint one = 1;

        // 消耗 22218 gas
    function test() public returns (uint) {
        return one + one + one;
    }
} 

contract Test {

    uint one = 1;

        // 消耗 22218 gas
    function test() public returns (uint) {
        return one + 1 + 1;
    }
}

因此我们不用另外增加一个内存变量来避免重复读取。

18、操作合约和数据合约分离

在使用工厂合约创建合约的情况下,可以将创建的合约分离为操作合约和数据合约。
操作合约只创建一次,工厂合约每次只创建数据合约,而不是每次都创建一整个合约,从而减少 gas 消耗。

19、将复杂的计算逻辑放在链下

考虑在链下进行复杂的计算逻辑,在链上存储结果。

20、尽量使用批量操作

因为一笔交易的基础 gas 消耗是 21000,批量操作相比多次操作,能减少多次的交易带来的基础 gas 消耗。

转载自:https://www.jianshu.com/p/330525940d79

以太坊一个区块可以包含多少交易

今天在做以太坊开发时,突然想到一个问题,以太坊一个区块包含的交易个数由什么决定?

如果交易池中有足够的交易,一个区块最多可容纳多少交易?

带着这个问题,我阅读了一下go-ethereum中关于挖矿的源代码,找到了答案。

先抛出结论:

影响一个区块交易个数的因素有两个:

  1. 交易池中的交易个数。这个很好理解,交易池的交易个数直接决定了一个矿工可以打包多少交易到区块中。
  2. 区块允许的GasLimit。GasLimit又由父块GasLimit、GasFloor和GasCeil共同决定(1.8版本以前只受父块GasLimit影响),当交易的gas总计大于GasLimit时,交易将不在打包的区块中。
    其中gasFloor和gasCeil是在geth的中mine的两个配置项。详见文档:https://geth.ethereum.org/docs/interface/command-line-options

结论主要从"go-ethereum/miner/worker.go"源文件中得出。worker相当于一个挖矿工人,负责具体的挖矿工作流程。在worker对象中有一个“commitNewWork”方法,是创建一个新的区块,其中计算了该区块的GasLimit和需要处理的交易池中的交易。相关代码如下:

func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
    // 创建区块头
    header := &types.Header{
        ParentHash: parent.Hash(),
        Number:     num.Add(num, common.Big1),
        // 计算GasLimit
        GasLimit:   core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil),
        Extra:      w.extra,
        Time:       uint64(timestamp),
    }

    // 从交易池中读取交易
    pending, err := w.eth.TxPool().Pending()
    if err != nil {
        log.Error("Failed to fetch pending transactions", "err", err)
        return
    }

    // Short circuit if there is no available pending transactions
    if len(pending) == 0 {
        w.updateSnapshot()
        return
    }

    // Split the pending transactions into locals and remotes
    localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
    for _, account := range w.eth.TxPool().Locals() {
        if txs := remoteTxs[account]; len(txs) > 0 {
            delete(remoteTxs, account)
            localTxs[account] = txs
        }
    }

    if len(localTxs) > 0 {
        txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
        if w.commitTransactions(txs, w.coinbase, interrupt) {
            return
        }
    }

    if len(remoteTxs) > 0 {
        txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
        if w.commitTransactions(txs, w.coinbase, interrupt) {
            return
        }
    }
}

“commitNewWork”方法中获取到交易池中的交易后将交易提交给了“commitTransactions”方法对交易进行验证。

func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool {
    // 将区块头中的GasLimit赋值给gasPool
    if w.current.gasPool == nil {
        w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit)
    }

    // 循环判断所有交易
    for {
        // gasPool中的gas小于21000是跳出循环
        if w.current.gasPool.Gas() < params.TxGas {
            log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
            break
        }

        // 按照交易gas从大到小的顺序获取下一个交易
        tx := txs.Peek()

        // 没有交易后跳出循环
        if tx == nil {
            break
        }
        // 提交交易
        logs, err := w.commitTransaction(tx, coinbase)
    }
}

将交易提交给“commitTransaction”方法后,回调用

receipt, _, err := core.ApplyTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vm.Config{})

在ApplyTransaction方法中会将gasPool中的gas值减去该笔交易的gas,当gasPool的gas值小于21000时,剩下的交易将不在打包的该区块中。

回头在来看一下区块中的GasLimit是怎么计算的。

core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil)方法在“go-ethereum/core/block_validator.go”,代码如下:

// CalcGasLimit computes the gas limit of the next block after parent. It aims
// to keep the baseline gas above the provided floor, and increase it towards the
// ceil if the blocks are full. If the ceil is exceeded, it will always decrease
// the gas allowance.
func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 {
    // contrib = (parentGasUsed * 3 / 2) / 1024
    contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor

    // decay = parentGasLimit / 1024 -1
    decay := parent.GasLimit()/params.GasLimitBoundDivisor - 1

    /*
        strategy: gasLimit of block-to-mine is set based on parent's
        gasUsed value.  if parentGasUsed > parentGasLimit * (2/3) then we
        increase it, otherwise lower it (or leave it unchanged if it's right
        at that usage) the amount increased/decreased depends on how far away
        from parentGasLimit * (2/3) parentGasUsed is.
    */
    limit := parent.GasLimit() - decay + contrib
    if limit < params.MinGasLimit {
        limit = params.MinGasLimit
    }
    // If we're outside our allowed gas range, we try to hone towards them
    if limit < gasFloor {
        limit = parent.GasLimit() + decay
        if limit > gasFloor {
            limit = gasFloor
        }
    } else if limit > gasCeil {
        limit = parent.GasLimit() - decay
        if limit < gasCeil {
            limit = gasCeil
        }
    }
    return limit
}

计算GasLimit的策略很有意思,受父块的GasLimit、挖矿的gas上限gasCeil和gas下限gasFloor三者共同决定。

当父块的交易里的gas总和大于父块GasLimit的2/3时,则增加当前块的GasLimit,反之减少当前块的GasLimit,同时,保证GasLimit的范围在gasFloor和gasCeil之间。

转载:https://www.jianshu.com/p/3588fd52ec0a

以太坊分片研究纲要

这是一个持续策划的列表,将添加或删除条目以反映与当前研究状态最相关的文章。

无国籍客户和证人

跨分片通信

基本信息和规格

权益证明理论

股权证明常见问题解答:https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ
Casper FFG 论文:https://arxiv.org/abs/1710.09437
基于证明委员会的完整 PoS 链:https://ethresear.ch/t/attestation-committee-based-full-pos-chains/2259

Casper FFG/GHOST/信标链模拟

卡斯帕CBC

CBC Casper 教程:https://vitalik.ca/general/2018/12/05/cbc_casper.html
Casper CBC,简化!:https://medium.com/@aditya.asgaonkar/casper-cbc-simplified-2370922f9aa6
信标链友好型 CBC Casper:https://ethresear.ch/t/beacon-chain-friendly-cbc-casper/4710/2
按位 LMD GHOST:https://ethresear.ch/t/bitwise-lmd-ghost/4749/5
LMD GHOST 实现:https://ethresear.ch/t/comparing-lmd-ghost-implementations/4945/3

外部链接

各种各样的

轻客户端

存储维护费/租金

监护证明

数据可用性证明

随机性

时间戳

数据结构

杂项杂项

英文原文:https://notes.ethereum.org/@serenity/H1PGqDhpm?type=view
翻译来自:谷歌

Trust EVM 让EOS支持以太坊合约

先记录下,做个笔记,翻译来自谷歌,有能力看原文。。
https://docs.trust.one/

Trust 是基于 EOS 网络的以太坊虚拟机,为开发者提供了一个交钥匙解决方案,可以在完全兼容以太坊的链上运行他们的应用程序,同时享受 EOS 的高吞吐量、可扩展性、安全性和可靠性,以及低交易成本 他们的用户。

由于设计的高度兼容性,以太坊原生应用程序可以无缝移植到 Trust。 开发人员在这里使用他们的 Solidity 智能合约时可能会喜欢熟悉的以太坊工具。

特点:

  • 确定性的Gas计算
  • 指令集级别的EVM兼容性
  • 完全的RPC兼容

技术实现

https://docs.trust.one/understanding-trust/architecture
EVM 作为 EOS 网络中的智能合约实现。 在 EVM 网络中生成交易是通过调用 EOS 网络上的 EVM 合约来完成的,EVM 的状态可以从 EOS 上的信息中得出。
为了实现完全 RPC 兼容性的目标,我们利用功能齐全的以太坊节点(当前设计中的 Geth)来提供所有读取 API,而所有写入访问将被转发到一个小型服务,以将它们打包到对 EVM 合约的 EOS 调用中。

我们所做的是设置一个“翻译器”服务,该服务读取运行在 EOS 上的 EVM 智能合约的共识输出,将该信息翻译成相应的 ETH 格式块并将这些块提供给 Geth 节点。 然后我们可以公开以太坊客户端 Web3 JSON RPC API(如有必要,还可以公开其他 API)。
如果我们发现它们更适合这种情况,我们也可能会在不同的场景中使用以太坊节点的其他实现。

未来的改进

当前设计正在运行并提供预期的兼容性级别。 还有一些潜在的方法可以改进整个系统:
合并 Translator 服务和 Geth 节点,去除相对不可靠的 p2p eth/66 通道。
将所有内容合并到一个 EOSIO 插件中,以便于部署。

MetaMask测试

https://docs.trust.one/about-the-testnet/connect-metamask

Network Name: Trust Network Testnet Preview
Chain ID: 15555
New RPC URL*: https://api.testnet-dev.trust.one
Currency Symbol: EVM
Block Explorer URL (Optional): https://trustscan.one

Faucet

https://faucet.testnet-dev.trust.one/

https://www.odaily.news/post/5178155
https://www.odaily.news/post/5178155

使用standard-input-json验证Solidity源码

使用 standard-input-json(以BSC测试链为例)

合约源码

Storage.sol

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/access/Ownable.sol";

contract Storage is Ownable {
    uint256 number;

    function setNumber(uint256 num) public onlyOwner {
        number = num;
    }

    function getNumber() public view returns (uint256) {
        return number;
    }
}

JSON格式的输入

Storage.json

{
    "language": "Solidity",
    "sources": {
        "contracts/Storage.sol": {
            "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.7.0 <0.9.0;\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract Storage is Ownable {\n    uint256 number;\n\n    function setNumber(uint256 num) public onlyOwner {\n        number = num;\n    }\n\n    function getNumber() public view returns (uint256) {\n        return number;\n    }\n}"
        },
        "@openzeppelin/contracts/access/Ownable.sol": {
            "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the deployer as the initial owner.\n     */\n    constructor() {\n        _transferOwnership(_msgSender());\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n        _;\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions anymore. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby removing any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
        },
        "@openzeppelin/contracts/utils/Context.sol": {
            "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n}\n"
        }
    },
    "settings": {
        "optimizer": {
            "enabled": true,
            "runs": 200
        },
        "outputSelection": {
            "*": {
                "*": [
                    "abi",
                    "evm.bytecode",
                    "evm.deployedBytecode",
                    "evm.methodIdentifiers",
                    "metadata"
                ],
                "": [
                    "ast"
                ]
            }
        }
    }
}

content内容:将solidity源码转换为JSON字符串。

Javascript Serializer(推荐方法2、方法3)

Convert/Escapes an object to a JSON string
将solidity源码转换为JSON字符串

方法1.BSC网址

去掉最后的 ”

方法2.字符串转义

// SPDX-License-Identifier: MIT\n\npragma solidity >=0.7.0 <0.9.0;\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract Storage is Ownable {\n    uint256 number;\n\n    function setNumber(uint256 num) public onlyOwner {\n        number = num;\n    }\n\n    function getNumber() public view returns (uint256) {\n        return number;\n    }\n}

方法3. Hardhat项目下

编译合约:

npx hardhat compile

打开artifacts\build-info路径下的json文件
input字段内容

部署合约

部署合约

与Storage.json配置文件相同

BSC浏览器验证合约

https://testnet.bscscan.com/address/0x1b3104004ebda264b88d04afb6ea66d70a2d51ac#code


  1. 转载自:https://learnblockchain.cn/article/3415