以太坊proxy合约

1. EIP 1052:智能合约验证

智能合约能够通过检查另一个智能合约的哈希值来验证其本身。在君士坦丁堡分叉之前,智能合约必须提取另一个合约的完整代码才能进行验证,这种验证方式将耗费大量的时间和资源。

智能合约需要验证另一个智能合约的bytecode,但是其并不需要知道具体的bytecode本身。如,智能合约想验证另一合约的bytecode为one of a set of permitted implementations,或者其需要分析code,若分析通过,则将其列入白名单中。
在君士坦丁堡分叉之前,合约可使用EXTCODECOPY(0x3c) opcode来实现,但是对于大合约,该操作尤其昂贵。尽管事实上其仅需要hash值就足够了。为此,在EIP 1052中引入了EXTCODEHASH(0x3f) opcode,会返回合约bytecode对应的keccak256 hash值。

EXTCODEHASH会从stake中取一个参数,然后在stack中存入:

  • 其前96个bit置为0,
  • 剩余的160bit为 the code of the account at the address对应的keccak256 hash值。

EXTCODEHASH(0x3f)的运行结果分为:

  • 若account不存在,或account为空,则执行结果为0;
  • 若该account没有code,则为keccak256(空)=c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470。可查看 SHA3在线生成器
	 /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following 
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly { codehash := extcodehash(account) }
        return (codehash != accountHash && codehash != 0x0);
    }

EXTCODEHASH的gas cost为400,后通过EIP-1884将该gas cost提升为700。

EIP-1884 :给与默克尔树大小相关的操作码重新定价。
改变了一些 EVM 操作码的 Gas 耗用量,以防止滥发交易攻击并更好地平衡每个区块的计算开销。在以太坊网络上,一个操作所需耗用的 Gas 数量往往跟这个操作所需付出的计算开销相匹配。该 EIP 提高了一些计算密集但当前的 Gas 耗用量较少的操作码的耗用量,即 SLOAD、BALANCE 以及 EXTCODEHASH。

EIP-1884 修改定价后,可能会导致transfer操作超过2300 gas limit,从而推荐使用transfer.{sendValue} 来转账,此时需注意reentrancy问题。

	/**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     *
     * _Available since v2.4.0._
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");
        // solhint-disable-next-line avoid-call-value
        (bool success, ) = recipient.call.value(amount)("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

2. 可升级Proxy合约

可参考:

在这里插入图片描述
实现可升级合约主要有2大策略:

  • 1)proxy合约
  • 2)将logic 和 data 分开为不同的合约。

Proxy合约使用delegatecall opcode来forward function calls to 可升级的目标合约。由于delegatecall保留了the function call状态,因此,可更新目标合约的逻辑,而在proxy合约中保留了已更新目标合约逻辑的状态。由于使用了delegatecall,msg.sender将保留the caller of the proxy contract。

关于可升级Proxy代理合约模式,截止2020年3月,相关研究有:
在这里插入图片描述

3. EIP 1967:Standard Proxy Storage Slots

在这里插入图片描述
proxy合约广泛用于:

  • 使合约可升级
  • 节约部署合约的gas费

proxy合约会通过delegatecall来调用logic合约。proxy合约会维护a persistent state (storage and balance) while the code delegated to the logic contract。

为了避免 proxy合约和logic合约 之间的存储使用冲突,logic合约 的地址通常保存在特定的storage slot 中,以保证编译器永远不会分配该插槽。EIP-1967 建议使用一组标准插槽来存储proxy信息。这允许像block explorers这样的客户端正确地提取并向最终用户显示这些信息,并允许logic合约选择性地对其进行操作。

具体为:

  • Logic合约地址:通过bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)获得。若为Beacon合约,则返回为空。
  • Beacon合约地址:通过bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)获得。若为Logic合约,则此处返回为空。
  • Admin地址:通过bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)获得。返回的为可升级logic合约的address。
contract Proxy {
  // Used to store the address of the owner.
  bytes32 private constant OWNER_POSITION = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1);
  // Used to store the address of the implementation contract.
  bytes32 private constant IMPLEMENTATION_POSITION = bytes32(
    uint256(keccak256("eip1967.proxy.implementation")) - 1
  );
  ......
  .......
}

参考资料

[1] SHA3在线生成器
[2] EIP-1052
[3] EIP-1884
[4] 从历次升级看以太坊协议的演化
[5] Proxy示例Solidity合约代码
[6] Summary of Ethereum Upgradeable Smart Contract R&D — Part 1–2018
[7] Summary of Ethereum Upgradeable Smart Contract R&D — Part 2 — 2020
[8] EIP-1967

猜你喜欢

转载自blog.csdn.net/mutourend/article/details/121148795