區塊鏈研究實驗室|如何在Solidity中設計模組化智慧合約

買賣虛擬貨幣

在本文中,我將描述如何使用稱為目標模式的東西來模組化智慧合約。使用標準的solidity,您將學習如何使用abi.encodeSelector和target.call(data)重寫脆弱、耦合的呼叫,以清除模組和分離關注點。為此,我們將以HumanityDAO為例 - 以及他們如何使用目標模式將治理與登錄檔分開。

HumanityDAO:介紹

HumanityDAO是一個DAO,其目的是人類驗證。維護一個說“你是真的”的登錄檔。它既能讓人安心,又在對抗Sybil攻擊方面也非常有用。在我目前的工作中,我正在 使用登錄檔構建像分散式reddit這樣的東西,你可以捎帶其他DAO所做的工作來打擊垃圾郵件(作為一個基本的例子)。一旦我們開始採用這些模式,您就可以看到整個生態系統如何蓬勃發展。

但除了HumanityDAO是一個偉大的dapp,它也是我看到的模組化Solidity設計的第一個例子。但是在這種情況下模組化指的是什麼?我們如何實現它?

HumanityDAO中的模組

HumanityDAO維護一個您新增和查詢的人的登錄檔,稱為HumanityRegology。登錄檔本身僅對單個對映(address=>bool)公共人員上操作。以下是新增條目的主要功能:

functionadd(address whopublic{
require(msg.sender == governance, 

"HumanityRegistry::add: Only governance can add an identity");
require(humans[who] == false

"HumanityRegistry::add: Address is already on the registry");

    _reward(who);
    humans[who] = true;
}

治理是我們將要研究的下一個模組,它涉及提案和投票。這是基本流程:

  1. Propose(address target, bytes memory data)開始一個新的提議。它會建立一個Proposal,將其新增到列表中,然後發出一個事件。

  2. 使用者呼叫voteyes(uint proposalid)和voteno(uint proposalid)在透過/失敗提案時使用其令牌。

  3. 投票期過後,可以呼叫finalize(uint proposalId)。如果proposal.yesCount> proposal.noCount,則使用資料呼叫target

為了簡單起見,最後一步我將在本指南中引用為目標模式。這不是一種新的程式設計模式.

EVM:呼叫合同,傳送訊息

簡要說明 - 以太坊合約在EVM中執行,這是一個訊息傳遞執行環境。因此,當您向地址傳送10個wei時,您傳送的訊息顯示{from:me,to:0x123456789,value:10}。當我們在合同上執行呼叫時,除了附加資料之外我們也是這樣做的 - 所以現在訊息看起來像{from:me,to:0x123456789,data:“0xcafebabe”,value:10}。這是什麼資料?嗯,這是根據Solidity應用程式二進位制介面(或簡稱ABI)編碼的呼叫。

訊息構造很好,因為它意味著傳送以太和呼叫智慧合約沒有什麼不同。將錢匯入使用者的錢包地址,並將錢匯入合同,看起來一樣。唯一的區別是,合同的地址不是從公鑰生成的(而是從nonce生成)。

因為我們在區塊鏈中,而且我們更願意以不變的方式進行引用,所以智慧合約中的方法由它們的選擇器引用。選擇器只是函式簽名雜湊的一部分。

target模式

目標模式非常簡單,但尚未被廣泛使用,因為Solidity具有這些特殊名稱。它也可以稱為動態排程(Ruby,Obj-C),回撥(JS),無論你想要什麼; 

它基本上包括:

  • 你正在構建的模組,例如治理

  • 你正在實施的事情,例如登錄檔

  • 目標整合點- 即傳遞提案將新增到登錄檔

我們將從HumanityDAO的例子中學習如何實現這一目標。

綜上所述,我們的目標是:在透過治理投票之後,在登錄檔中新增一個條目。


在我們將它們組合在一起之前,下面是兩個難題,治理和註冊模組:

contract Registry {
    mapping (address => bool) public humans

functionadd(address whopublic{
require(msg.sender == governance, "HumanityRegistry::add: Only governance can add an identity");
        humans[who] = true;
    }
}

contract Governance {
functionpropose(address target, bytes memory data

publicreturns (uint{

        uint proposalId = proposals.length;
        Proposal memory proposal;
        proposal.target = target;
        proposal.data = data;
        proposals.push(proposal);
    }

functionvote() public/* ... */ }

functionfinalize(uint proposalIdpublic{
        Proposal storage proposal = proposals[proposalId];
require(proposal.result == Result.Pending, 

"Governance::finalize: Proposal is already finalized");

if (proposal.yesCount > proposal.noCount) {
// Call the target contract with the data
            proposal.target.call(proposal.data);
        }
    }
}

我們可以使用Registry.add新增到登錄檔中。治理的基礎是提交一份提案,對提案進行投票,然後呼叫finalize並透過/失敗。如果提案透過,我們將資料中的calldata呼叫target合同。

那麼我們如何構建這個calldata呢? 你會認為這是非常複雜的,即Solidity

不! 我們可以透過一個名為abi.encodeWithSelector的東西來做到這一點:

bytes memory data = abi.encodeWithSelector(registry.add.selector, who);
governance.propose(address(registry), data)

就像那樣,目標被設定為登錄檔合同,並且看起來像Registry.add(who)的呼叫被編碼並儲存在提議中。 這真的很簡單。

使用wrapper

還有一件事 - 因為我們已經在談論良好的設計和可組合性 - 我建議在一個單獨的合同中用一種方法wrapper這個呼叫。 這個wrapper智慧合約是一個可以在前端呼叫的簡單方法。

我們將建議的程式碼流命名為什麼,並可能將某人新增到登錄檔中? HumanityDAO稱之為HumanityApplicant:

contract HumanityApplicant {
    Governance governance;
    Registry registry;

constructor(Governance _Governance, Registry _Registry) {
        governance = Governance(_Governance);
        registry = Registry(_Registry);
    }

functionaddToRegistry(address who{
        bytes memory data = abi.encodeWithSelector(registry.add.selector, who);
return governance.propose(msg.sender, address(registry), data);
    }
}

結論

就是這樣! 使用此功能,您可以輕鬆獲取治理合同並在您自己的設計中使用它。

免責聲明:

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

推荐阅读

;