在上一篇文章中(傳送門:區塊鏈研究實驗室 | 智慧合約如何建立和繼承),我們討論瞭如何從另一個智慧合約中建立一個智慧合約。今天,我們將研究這種情況下的典型用例。
什麼是工廠模式?
工廠模式的想法是擁有一個合同(工廠),該合同將承擔建立其他合同的任務。在基於類的程式設計中,此模式的主要動機來自於單一職責原則(一個類不需要知道如何建立其他類的例項),並且該模式為建構函式提供了一種抽象。
為什麼要牢固使用工廠模式?
在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;
}
}
這能夠節省部署成本,您部署工廠模式,以後再使用它來部署其他合同,並且可以提高合同安全性。
如何與已部署的智慧合約進行互動
在深入探討如何實現工廠模式的細節之前,我想澄清一下我們與已部署的智慧合約進行互動的方式。工廠模式是關於建立子合同的,我們可能希望呼叫它們的某些功能以更好地管理這些合同。
當我們要呼叫已部署的智慧合約時,需要做兩件事:
合同的ABI(提供有關功能簽名的資訊)。如果合同在同一個專案中。您可以使用import關鍵字將其匯入。
部署合同的地址。
讓我們舉個例子:
contract A {
address bAddress;
constructor(address b){
bAddress = b;
}
functioncallHello() externalviewreturns(string memory){
B b = B(bAddress); // explicit conversion from address to contract type
return 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: MIT
pragma 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);
});
});
});
結論
至此,我們這個系列已經完結
作者:鏈三豐,來源:區塊鏈研究實驗室