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

買賣虛擬貨幣

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

什麼是工廠模式?

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

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

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

contract Factory { Child[] children; function createChild(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; }  function callHello() external view returns(string memory){ B b = B(bAddress); // explicit conversion from address to contract type return b.sayHello(); }}contract B { string greeting = "hello world"; function sayHello() external view returns(string memory){ return greeting; }}

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

正常工廠模式

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

contract Factory{ Child[] public children; uint disabledCount;
event ChildCreated(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; bool public isEnabled; uint public 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 public data; // 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", async function () { 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. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读