以太坊使用最小Gas克隆合约-合约工厂

区块链资讯登链社区2020-09-15 19:14:38  阅读 -评论 0

以太坊中,大部分的业务场景对智能合约的要求都是部署一次,但也有些场景,需要根据不同情况动态部署合约,比如在交易所中,为每个用户部署一个充提合约。对于第二种情况,往往需要方便并且低成本去生成和部署合约。类似编程中常见的工厂模式,不需要关系的对象的具体创建逻辑,只需要根据暴露的接口就可以创建出想要的对象。solidity也有类似的工厂,分为普通工厂和克隆工厂。
一、普通工厂
普通工厂,就是在工厂合约中以new的方式创建一个新合约。我这里以MetaCoin合约示例,合约代码如下所示。
pragma solidity ^0.5.0;
contract MetaCoin {
 mapping (address => uint) balances;
 constructor(address metaCoinOwner, uint256 initialBalance) public {
  balances[metaCoinOwner] = initialBalance;
 }
 function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
  if (balances[msg.sender] < amount) return false;
  balances[msg.sender] -= amount;
  balances[receiver] += amount;
  return true;
 }
 function getBalance(address addr) view public returns(uint) {
       return balances[addr];
 }
}
contract MetaCoinFactory {
    MetaCoin[] public metaCoinAddresses;
    event MetaCoinCreated(MetaCoin metaCoin);
    address private metaCoinOwner;
    constructor(address _metaCoinOwner ) public {
        metaCoinOwner = _metaCoinOwner ;
    }
    function createMetaCoin(uint256 initialBalance) external {
        MetaCoin metaCoin = new MetaCoin(metaCoinOwner, initialBalance);
        metaCoinAddresses.push(metaCoin);
        emit MetaCoinCreated(metaCoin);
    }
    function getMetaCoins() external view returns (MetaCoin[] memory) {
        return metaCoinAddresses;
    }
}
在MetaCoinFactory工厂合约中, createMetaCoin方法中使用new创建MetaCoin新合约,并将得到的合约地址存储在metaCoinAddresses数组中。这种方式的优点就是简单,通过工厂部署的合约是一个独立的合约,相关的交易信息在浏览器上可查。缺点就是手续费太高。
二、克隆工厂
如果每次部署的合约都一样,那就没必要对合约的字节码重新部署,耗费手续费。基于这一思想,以太坊提出了EIP1167,最小代理合约,底层根据delegatecall,将克隆出来的合约调用都委派到一个已知的固定合约地址中。
先来看一个例子,还是以MetaCoin为例,这里方便演示,我把多个合约合并到了一个文件中,合约代码如下所示。
pragma solidity ^0.5.0;
contract MetaCoinClonable {
 mapping (address => uint) balances;
    
    function initialize(address metaCoinOwner, uint256 initialBalance) public {
        balances[metaCoinOwner] = initialBalance;
    }
    
 function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
  if (balances[msg.sender] < amount) return false;
  balances[msg.sender] -= amount;
  balances[receiver] += amount;
  return true;
 }
 function getBalance(address addr) view public returns(uint) {
       return balances[addr];
 }
}
contract Ownable {
  /**
   * @dev Event to show ownership has been transferred
   * @param previousOwner representing the address of the previous owner
   * @param newOwner representing the address of the new owner
   */
  event OwnershipTransferred(address previousOwner, address newOwner);
  // Owner of the contract
  address private _owner;
  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner());
    _;
  }
  /**
   * @dev The constructor sets the original owner of the contract to the sender account.
   */
  constructor() public {
    setOwner(msg.sender);
  }
  /**
   * @dev Tells the address of the owner
   * @return the address of the owner
   */
  function owner() public view returns (address) {
    return _owner;
  }
  /**
   * @dev Sets a new owner address
   */
  function setOwner(address newOwner) internal {
    _owner = newOwner;
  }
  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    emit OwnershipTransferred(owner(), newOwner);
    setOwner(newOwner);
  }
}
// https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol
contract CloneFactory {
  function createClone(address target) internal returns (address result) {
    bytes20 targetBytes = bytes20(target);
    assembly {
      let clone := mload(0x40)
      mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
      mstore(add(clone, 0x14), targetBytes)
      mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
      result := create(0, clone, 0x37)
    }
  }
  function isClone(address target, address query) internal view returns (bool result) {
    bytes20 targetBytes = bytes20(target);
    assembly {
      let clone := mload(0x40)
      mstore(clone, 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000)
      mstore(add(clone, 0xa), targetBytes)
      mstore(add(clone, 0x1e), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
      let other := add(clone, 0x40)
      extcodecopy(query, other, 0, 0x2d)
      result := and(
        eq(mload(clone), mload(other)),
        eq(mload(add(clone, 0xd)), mload(add(other, 0xd)))
      )
    }
  }
}
contract MetaCoinCloneFactory is CloneFactory, Ownable {
    MetaCoinClonable[] public metaCoinAddresses;
    event MetaCoinCreated(MetaCoinClonable metaCoin);
    address public libraryAddress;
    address public metaCoinOwner;
    function setLibraryAddress(address _libraryAddress) external onlyOwner {
        libraryAddress = _libraryAddress;
    }
    function createMetaCoin(address _metaCoinOwner, uint256 initialBalance) external {
        MetaCoinClonable metaCoin = MetaCoinClonable(
            createClone(libraryAddress)
        );
        metaCoin.initialize(_metaCoinOwner, initialBalance);
        metaCoinAddresses.push(metaCoin);
        emit MetaCoinCreated(metaCoin);
    }
    function getMetaCoins() external view returns (MetaCoinClonable[] memory) {
        return metaCoinAddresses;
    }
}
部署流程:
1. 先部署MetaCoinClonable合约,得到地址如0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
2. 部署MetaCoinCloneFactory合约,得到地址如0xbbf289d846208c16edc8474705c748aff07732db
3. 调用setLibraryAddress方法,参数为MetaCoinClonable的合约地址。
4. 调用createMetaCoin方法,创建MetaCoin新合约。
5. 调用getMetaCoins方法,可获取已创建的MetaCoin合约地址,如得到一个地址0xe5240103E1Ff986A2C8aE6B6728FFe0d9a395C59

6. 使用MetaCoin合约地址0xe5240103E1Ff986A2C8aE6B6728FFe0d9a395C59调用MetaCoinClonable合约的getBalance方法,即可得到对应地址初始化时的数量,如下图所示。

基本原理
克隆工厂核心是CloneFactory合约,在createClone方法中,使用solidity的内联汇编(assembly)来克隆合约。
· let clone := mload(0x40)在 Solidity 中,内存插槽 0x40 位置是比较特殊的,它包含了下一个可用的空闲内存指针的值。每次将变量直接保存到内存时,都应通过查询 0x40 位置的值,来确定变量保存在内存的位置。
· mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000),这句的意思是将0x3d...的保存在了clone指针指向的位置。
· mstore(add(clone, 0x14), targetBytes),将clone的指针向后移动0x14(20)个字节,在保存targetBytes(20字节)的值。我们上边部署MetaCoinClonable合约,得到targetBytes的值是0x692a70d2e424a56d2c6c27aa97d1a86395877b3a,此时clone指向的空间存储的内容为0x3d602d80600a3d3981f3363d3d373d3d3d363d73+692a70d2e424a56d2c6c27aa97d1a86395877b3a
· mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000),将clone的指针向后移动0x28(40)个字节,然后存证0x5af43...的值,此时clone指向的空间存储的内容为0x3d602d80600a3d3981f3363d3d373d3d3d363d73+692a70d2e424a56d2c6c27aa97d1a86395877b3a+5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
· result := create(0, clone, 0x37),create操作码的功能是根据指定的合约字节码创建新合约,并返回合约地址。第一个参数0代表发送的以太币个数;第二个参数clone指合约字节码的起始位置;0x37(55)指合约字节码的终止位置。新合约的字节码就是0x3d602d80600a3d3981f3363d3d373d3d3d363d73692a70d2e424a56d2c6c27aa97d1a86395877b3a5af43d82803e903d91602b57fd5bf3。可以通过eth_getCode获取我们上边得到的克隆出来的合约0xe5240103E1Ff986A2C8aE6B6728FFe0d9a395C59的字节码比对,是一样的。
在合约字节码中3d602d80600a3d3981f3是EIP-1167标准克隆协议部署的一部分,固定不变。其余对应的EVM操作码如下图所示。

使用这种有以下需要注意的地方:
· 被克隆的合约不能有构造函数,MetaCoinClonable合约使用initialize方法替代了构造函数。
· 克隆工厂MetaCoinCloneFactory合约中的母合约libraryAddress可以被替换,替换后之前已克隆出的合约不受影响,新克隆合约将以新的母合约克隆。
· 用于克隆的母合约如果销毁了,则克隆出的合约将不可用。
三、参考
https://eips.ethereum.org/EIPS/eip-1167 https://github.com/optionality/clone-factory/issues/10 https://soliditydeveloper.com/clonefactory

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们kefu@lianshijie.com

    参与讨论 (0 人参与讨论)

    相关推荐

    中信银行打造“区块链”信用证结算!

    中信银行打造“区块链”信用证结算!

    科技不会改变金融的实质,但却能让金融服务更高效,能让资金供、需方信息不对称的问题更好地解决。近期,中信银行首个区块链项目——基于区块链的国内信用证信息传输系统(简称BCLC)(一期)成功上线,这是国内银行业第一次将区块链技术应用于信用证结算领域。 据中信银行国际业务部总经理助理张栩青介绍,将现在流行的区块链技术应用在国内信用证中,改变了银行传统信用证业务模式,信用证的开立、通知、交单、承兑报文

    中国信息技术部门成立区块链研究实验室

    中国信息技术部门成立区块链研究实验室

    暴走时评:本月初,中国政府对国内的ICO和数字货币交易所的打击在世界范围内引起了强大反响,但政府已经多次声明不会将区块链与数字货币划等号,依然非常重视区块链技术在中国的发展。鉴于中国工业和信息化部成立了一个专门研究区块链的实验室,这一论调也得到了进一步的证实。 虽然中国政府最近在大力打击比特币交易所和ICO,但仍然致力于开发区块链在其他领域的潜力。 据财新网报道,中国工业和信息化部已经成立了一

     分布式账本中的生命科学

    分布式账本中的生命科学

    生物科学是医学领域涉及遗传研究,疾病预防和生活方式治疗(lifestyle treatments)的学科。它已经存在了很长时间,但区块链技术的基础设施应用给该学科提供了重大进步的可能性。 根据Pistoia Alliance进行的2016年6月份高级制药和生命科学领袖调查,83%的受访者表示,他们预计在五年内将全面采用区块链技术。 Pistoia Alliance是一个全球性的非营利组织,致

    区块链vs.核能:日本最大电力公司东京电力(TEPCO)寻求使用区块链减轻对核电的依赖

    区块链vs.核能:日本最大电力公司东京电力(TEPCO)寻求使用区块链减轻对核电的依赖

    东京电力公司 (TEPCO) 对于能源过度中心化的风险可以说绝不陌生。 也许最著名的就是2011年发生的福岛核电站事故,这个日本最大的能源公司如今正在寻求区块链技术来防止这种灾难再次发生。 然而,从使用微型风车的分布式风力发电到用于存储在电力成本低时购买的电力的智能电池,可替代能源项目一直以来都属于个人慈善事业。 然而,TEPCO风险投资部门主管Jeffrey Char认为区块链能够帮助为这

    继证监会发表代币发行声明之后,香港交易所Gatecoin将下线部分ICO币

    继证监会发表代币发行声明之后,香港交易所Gatecoin将下线部分ICO币

    经过一系列监管以及合规审查后,香港交易所Gatecoin将会下线那些被金融监管部门定性为"证券"的代币。 香港加密货币交易所Gatecoin透露,如果在该平台交易的ICO代币在法律上符合"证券"定义,他们就会下线这些代币。据巴比特上月报道,香港主要的金融监管部门证券及期货事务监察委员会(SFC)表达了对ICO这种日渐普及的募资模式的担忧。 尽管ICO中售卖的数字代币通常都被定义为虚拟商品,但

    IBM与超级账本共同加入去中心化身份基金会(DIF),推动创建区块链ID行业标准

    IBM与超级账本共同加入去中心化身份基金会(DIF),推动创建区块链ID行业标准

    IBM与超级账本已经签署协议加入去中心化身份基金会(DIF),这个于今年初成立的联盟旨在帮助推动基于区块链的ID系统的互操作性和标准。 这两个企业区块链大佬加入了这个有各种企业组成的团体,其中包括像微软和埃森哲这样的大企业,还有像Civic和Gem这样的创业公司,以及像uPort和Sovrin这样的开源项目。 DIF执行主管告诉Coindesk说: "这应该是一个信号,表明在这一领域有广泛的

    为打击人口贩卖,牙买加警方盯上了犯罪分子的比特币钱包

    为打击人口贩卖,牙买加警方盯上了犯罪分子的比特币钱包

    作为打击人口贩卖计划的一部分,牙买加警方已经开始行动,锁定了那些试图用比特币和数字支付来掩人耳目的犯罪分子。 越来越多的人口贩卖者都开始转向数字货币来帮助他们进行地下活动并接收非法活动所得,但牙买加警方已经盯上他们了。 牙买加的'大生意' 不幸的是,人口贩卖以及性奴市场规模十分庞大,预计涉资1500亿美元。在牙买加,大约有7000个妇女、儿童以及成年男性被奴役,他们的操控者出售奴役服务的价格

    深圳市将发布《深圳市扶持金融业发展若干措施》,奖励区块链、数字货币等金融创新

    10月9日,深圳市人民政府向各区人民政府,市政府直属各单位印发《深圳市扶持金融业发展若干措施》(以下简称"《若干措施》")。深圳市政府表示,此举是为进一步完善金融支持政策体系,吸引集聚优质金融资源,推动全市金融业可持续均衡发展,加快建设国际化金融创新中心。 《若干措施》共分五大项,33条。内容包括:坚持服务导向,优化金融政策环境;发展金融总部经济,鼓励金融总部企业做大做强;支持金融企业分支机构

    麦妖榜
    更新日期 2019-09-03
    排名用户贡献值
    1牛市来了30910
    2BitettFan24187
    3等待的宿命23810
    4区块大康20369
    5六叶树20310
    6linjm122719429
    7天下无双16192
    8lizhen00215280
    9让时间淡忘14586
    10yelanyi050511349
    返回顶部 ↑