區塊鏈研究實驗室 | 如何在智慧合約中使用工廠模式

買賣虛擬貨幣

在上一篇文章中(傳送門:區塊鏈研究實驗室 | 智慧合約如何建立和繼承),我們討論瞭如何從另一個智慧合約中建立一個智慧合約。今天,我們將研究這種情況下的典型用例。

什麼是工廠模式?

工廠模式的想法是擁有一個合同(工廠),該合同將承擔建立其他合同的任務。在基於類的程式設計中,此模式的主要動機來自於單一職責原則(一個類不需要知道如何建立其他類的例項),並且該模式為建構函式提供了一種抽象。

為什麼要牢固使用工廠模式?

在Solidity中,出於以下原因之一,您可能要使用工廠模式:如果要建立同一合同的多個例項,並且正在尋找一種跟蹤它們並簡化其管理的方法。

    contract Factory { Child[] children;functioncreateChild(uint data){ Child child = new Child(data); children.push(child); }}contract Child{ uint data;constructor(uint _data){ data = _data; }}

    這能夠節省部署成本,您部署工廠模式,以後再使用它來部署其他合同,並且可以提高合同安全性。

    如何與已部署的智慧合約進行互動

    在深入探討如何實現工廠模式的細節之前,我想澄清一下我們與已部署的智慧合約進行互動的方式。工廠模式是關於建立子合同的,我們可能希望呼叫它們的某些功能以更好地管理這些合同。

    當我們要呼叫已部署的智慧合約時,需要做兩件事:

    1. 合同的ABI(提供有關功能簽名的資訊)。如果合同在同一個專案中。您可以使用import關鍵字將其匯入。

    2. 部署合同的地址。

    讓我們舉個例子:

      contract A { address bAddress;constructor(address b){ bAddress = b; }functioncallHello() externalviewreturns(string memory){ B b = B(bAddress); // explicit conversion from address to contract typereturn b.sayHello(); }}contract B {string greeting = "hello world";functionsayHello() externalviewreturns(string memory){return greeting; }}

      在Remix中,首先部署合約B,然後複製其地址,並在部署時將其提供給A的建構函式。現在,您可以呼叫該callHello()函式,您將獲得sayHello()合約B的函式結果。

      正常工廠模式

      在這種模式下,我們建立具有處理子合同建立功能的工廠合同,並且可能還會新增其他功能來有效管理這些合同(例如,查詢特定合同或禁用合同)。在create函式中,我們使用new關鍵字來部署子合同。

        contract Factory{ Child[] public children;uint disabledCount;
        eventChildCreated(address childAddress, uint data);
        function createChild(uint data) external{ Child child = new Child(data, children.length); children.push(child);emit ChildCreated(address(child), data); }
        function getChildren() external view returns(Child[] memory _children){ _children = new Child[](children.length- disabledCount);uint count;for(uint i=0;i<children.length; i++){if(children[i].isEnabled()){ _children[count] = children[i]; count++; } } }
        function disable(Child child) external { children[child.index()].disable(); disabledCount++; }}contract Child{uint data;boolpublic isEnabled;uintpublic index; constructor(uint _data,uint _index){ data = _data; isEnabled = true; index = _index; }
        function disable() external{ isEnabled = false; }}

        克隆工廠模式

        先前模式的問題在於,由於所有子合同都具有相同的邏輯,並且每次我們幾乎都重新部署了相同的合同-相同的程式碼但上下文不同,因此浪費了大量的精力。我們需要一種方法來僅部署一個具有所有功能的子合同,並使所有其他子合同充當代理,以將呼叫委派給我們建立的第一個子合同,並讓功能在代理合同的上下文中執行。

        幸運的是,有一個EIP-1167規範定義瞭如何廉價地實現代理合同。該代理將所有呼叫和100%的天然氣轉發給實施合同,然後將返回值中繼回撥用者。根據規範,代理合同的位元組碼為:

        363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3。

        索引10-29(含10-29)處的位元組被替換為主功能合同(我們將委派呼叫的合同)的20位元組地址。

        使用可以完成代理合同的全部魔力delegatecall。透過閱讀本文,您可以瞭解其工作原理。

        現在讓我們看看如何進行這項工作:

          //SPDX-License-Identifier: MITpragma solidity ^0.7.0;
          import'./CloneFactory.sol';
          contract Factory is CloneFactory { Child[] public children; address masterContract;
          constructor(address _masterContract){ masterContract = _masterContract; }
          function createChild(uint data) external{ Child child = Child(createClone(masterContract)); child.init(data); children.push(child); }
          function getChildren() external view returns(Child[] memory){return children; }}
          contract Child{ uint publicdata;// use this function instead of the constructor// since creation will be done using createClone() function function init(uint _data) external {data = _data; }}

          這次,我們使用了createCloneGitHub儲存庫中的函式來建立子合同,而不是new關鍵字。

          您可以透過在Truffle中建立一個新的遷移檔案來部署合同,如下所示:

            const Child = artifacts.require("Child");const Factory = artifacts.require("Factory"); module.exports = function(_deployer)_deployer.deploy(Child).then(() => _deployer.deploy(Factory, Child.address)); };

            為了測試程式碼是否有效,我建立了一個測試檔案,您可以自行嘗試確保所有檔案均按預期工作:

              contract("Factory", function (/* accounts */) { it("should assert true", asyncfunction () {await Factory.deployed();return assert.isTrue(true); });
              describe("#createChild()",async () => {let factory; beforeEach(async ()=>{ factory = await Factory.deployed(); });
              it("should create a new child", async () => {await factory.createChild(1);await factory.createChild(2);await factory.createChild(3);const children = await factory.getChildren();//console.log(children);const child1 = await Child.at(children[0]);const child2 = await Child.at(children[1]);const child3 = await Child.at(children[2]);
              const child1Data = await child1.data();const child2Data = await child2.data();const child3Data = await child3.data();
              assert.equal(children.length, 3); assert.equal(child1Data, 1); assert.equal(child2Data, 2); assert.equal(child3Data, 3);
              }); });});


              結論

              至此,我們這個系列已經完結

              作者:鏈三豐,來源:區塊鏈研究實驗室

              免責聲明:

              1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
              2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
              3. 鏈報僅提供相關項目信息,不構成任何投資建議

              推荐阅读

              ;