1. 程式人生 > >什麼是以太坊DAO?(一)

什麼是以太坊DAO?(一)

Decentralized Autonomous Organization,簡稱DAO,以太坊中重要的概念。一般翻譯為去中心化的自治組織。

“在區塊鏈上,沒有人知道你是一臺冰箱”——理查德布朗

到目前為止,我們列出的所有合約都是由人類持有的其他賬戶擁有和執行的。但是在以太坊生態系統中不存在對機器人或人類的歧視,合約可以像任何其他帳戶一樣創造任意行為。合約可以擁有代幣,參與眾籌,甚至是其他合約的投票成員。

在本節中,我們將建立一個去中心化的民主組織機構,僅存在於區塊鏈上,但這可以做任何簡單賬戶所能做到的事情。該組織有一箇中央經理,負責決定誰是成員和投票規則,但正如我們所看到的,這也可以改變。

這種特殊民主的運作方式是它擁有一個像管理員,執行長或總統一樣工作的所有者Owner

。所有者可以向組織新增(或刪除)投票成員。任何成員都可以提出一個提議,該提議以以太坊交易的形式傳送以太或執行某些合約,其他成員可以投票支援或反對該提案。一旦預定的時間量和一定數量的成員投票,就可以執行提案:合約計票,如果有足夠的票數,它將執行給定的交易。

區塊鏈大會

文末附全部程式碼。

pragma solidity >=0.4.22 <0.6.0;

contract owned {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) onlyOwner  public {
        owner = newOwner;
    }
}

contract tokenRecipient {
    event receivedEther(address sender, uint amount);
    event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData);

    function receiveApproval(address _from, uint256 _value, address _token, bytes memory _extraData) public {
        Token t = Token(_token);
        require(t.transferFrom(_from, address(this), _value));
        emit receivedTokens(_from, _value, _token, _extraData);
    }

    function () payable external {
        emit receivedEther(msg.sender, msg.value);
    }
}

interface Token {
    function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
}

contract Congress is owned, tokenRecipient {
    // Contract Variables and events
    uint public minimumQuorum;
    uint public debatingPeriodInMinutes;
    int public majorityMargin;
    Proposal[] public proposals;
    uint public numProposals;
    mapping (address => uint) public memberId;
    Member[] public members;

    event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
    event Voted(uint proposalID, bool position, address voter, string justification);
    event ProposalTallied(uint proposalID, int result, uint quorum, bool active);
    event MembershipChanged(address member, bool isMember);
    event ChangeOfRules(uint newMinimumQuorum, uint newDebatingPeriodInMinutes, int newMajorityMargin);

    struct Proposal {
        address recipient;
        uint amount;
        string description;
        uint minExecutionDate;
        bool executed;
        bool proposalPassed;
        uint numberOfVotes;
        int currentResult;
        bytes32 proposalHash;
        Vote[] votes;
        mapping (address => bool) voted;
    }
.....

如何部署

開啟錢包(如果你只是測試,請轉到選單開發>網路>testnet),轉到合約選項卡Contracts,然後點選部署合約deploy contract ,在solidity code box上貼上上面的程式碼。在合約選擇器上,選擇Congress ,你將看到設定變數。

  • 提案的最低法定人數是提案在執行之前需要的最低票數。
  • 爭論的時間是在執行之前需要經過的最短時間(以分鐘為單位)。
  • 多數票的保證金如果超過50%的票數加上保證金,則提案通過。在簡單多數時保留為0,將其設為成員數-1要求絕對共識。

你可以稍後更改這些引數。首先,你可以選擇5分鐘進行辯論,並將剩餘引數保留為0。在頁面上稍微低一點,你將看到在乙太網中部署合約的成本估算值。如果要儲存,可以嘗試降低價格,但這可能意味著必須等待更長時間才能建立合約。 單擊部署Deploy

,鍵入密碼並等待。

幾秒鐘後,你將被帶到儀表板dashboard,向下滾動,你將能夠看到正在建立的交易。在不到一分鐘的時間內,你將看到交易成功,並且將建立一個新的唯一圖示。單擊合約的名稱以檢視它(你可以隨時在合約選項卡Contracts上找到它)。

與他人分享

如果你想與他人共享你的DAO,那麼他們需要合約地址和介面檔案,這是一個小文字字串,作為合約的使用說明書。單擊複製地址copy address以獲取前者並 顯示介面show interface以顯示後者。

在另一臺計算機上,進入合約選項卡Contracts,然後單擊監視合約watch contract 。新增正確的地址和介面,然後單擊OK

與合約互動

在從合約中讀取Read from contract中,你可以看到合約中可以免費執行的所有功能,因為它們只是從區塊鏈中讀取資訊。例如,你可以在此處檢視合約的當前所有者owner(應該是上載合約的帳戶)。

在寫入合約Write to contract中,你有一個列表,其中列出了將嘗試進行某些計算以將資料儲存到區塊鏈的所有函式,因此將花費以太。選擇New Proposal,它將顯示該功能的所有選項。

在與合約互動之前,你需要新增新成員才能投票。 在Select function選擇器上,選擇Add Member。新增你要成為會員的人的地址(要刪除會員,請選擇Remove Member功能)。 在execute from時,請確保你擁有與所有者相同的帳戶,因為這只是主要管理員可以執行的操作。點選執行execute並等待幾秒鐘,以便下一個塊進行更改。

沒有成員列表,但你可以通過將其地址放在Read from contract列的Members功能上來檢查是否有人是成員。

此外,如果你想讓合約有自己的錢,你需要存入一些以太(或其他代幣),否則你將擁有一個非常無聊的組織。按右上角的transfer Ether & Tokens

新增一個簡單的提案:傳送以太

現在讓我們將第一個提案新增到合約中。在函式選擇器上,選擇New Proposal

對於“受益人”,新增你要傳送以太的人的地址,並在標有“Wei Amount”的框中輸入你要傳送的數量。Wei是以太的最小單位,等於10^-18以太,並且必須始終作為整數給出。例如,如果要傳送1以太,請輸入1000000000000000000(即18個零)。最後,新增一些描述你要執行此操作的原因的文字。暫時將“Transaction bytecode”留空。單擊執行execute並鍵入密碼。幾秒鐘後,numProposals將增加到1,第一個提案編號0將出現在左列上。當你新增更多提案時,只需將提案編號放在proposals欄位中即可看到其中的任何提案,你可以閱讀所有提案。

投票提案也很簡單。在函式選擇器上選擇Vote。在第一個框中鍵入提議編號,如果你同意,請選中Yes框(或將其留空以對其進行投票)。點選execute傳送你的投票。

投票時間過後,你可以選擇executeProposal。如果提案只是傳送以太,那麼你也可以將transactionBytecode欄位留空。在點選execute之後但在輸入密碼之前,請注意出現的螢幕。

如果estimated fee consumption即估計費用消耗欄位上有警告,則表示由於某種原因,被呼叫的函式將不會執行並將突然終止。這可能意味著許多事情,但在本合約的上下文中,只要你在截止日期過後嘗試執行合約,或者使用者嘗試傳送的位元組碼資料與原始提案不同,就會顯示此警告。出於安全原因,如果發生任何這些事情,合約執行將突然終止,並且嘗試非法交易的使用者將失去他傳送的用於支付交易費用的所有以太費用。

如果交易被執行,那麼幾秒鐘之後你應該能夠看到結果:執行將變為真,並且應該從該合約的餘額和收件人地址中減去正確的以太量。

新增複雜提案:擁有另一個代幣

你可以使用此民主方式來執行以太坊上的任何交易,只要你能夠找出該交易生成的位元組碼即可。幸運的是,你可以使用錢包做到這一點!

在這個例子中,我們將使用一個代幣來表明這個合約可以容納多於以太,並且可以在任何其他基於以太坊的資產中進行交易。首先,建立一個屬於你的普通帳戶的代幣 。在合約頁面上,單擊轉移乙太網和代幣以將其中一些轉移到新的congress合約中(為簡單起見,不要將超過一半的硬幣傳送到你的DAO)。之後,我們將模擬你要執行的操作。因此,如果你想建議DAO向個人傳送500毫克黃金代幣作為付款,請按照你從你擁有的帳戶執行該交易的步驟,然後點選send但是當確認螢幕時彈出,不要輸入密碼 。

而是單擊顯示原始資料SHOW RAW DATA連結並複製RAW DATA欄位上顯示的程式碼並將其儲存到文字檔案或記事本中。取消交易。你還需要你將要為該操作呼叫的合約的地址,在這種情況下是代幣合約。你可以在Contracts選項卡上找到它:將其儲存在某處。

現在回到congress合約並使用以下引數建立新提案:

  • 作為受益人,請填寫你的代幣地址(如果它是相同的圖示請注意)。
  • 將以太幣量留空。
  • 在工作描述上,只需寫下你想要完成內容的描述。
  • Transaction Bytecode上 ,貼上你在上一步中從資料欄位中儲存的位元組碼。

幾秒鐘後,你應該能夠看到提案的詳細資訊。你會注意到交易位元組碼不會在那裡顯示,而是隻有一個交易雜湊transaction hash。與其他欄位不同,位元組碼可能非常冗長,因此儲存在區塊鏈上非常昂貴,因此稍後執行呼叫的人將提供位元組碼,而不是對其進行存檔。

但是,這當然會造成一個安全漏洞:如果沒有實際程式碼,投票如何投票?什麼阻止使用者在提案投票後執行不同的程式碼?這就是交易雜湊的用武之地。在從合約中讀取read from contract功能列表中滾動一下,你會看到一個提議檢查功能,任何人都可以放置所有的功能引數並檢查它們是否與被投票的匹配。這也保證了除非位元組碼的hash與提供的程式碼上的hash完全匹配,否則不會執行提議。

任何人都可以通過遵循相同的步驟來獲取正確的位元組碼,然後將提議編號和其他引數新增到從合約中讀取read from contract底部的名為 檢查提案程式碼Check proposal code的功能,從而可以非常輕鬆地檢查提案。

其餘的投票過程保持不變:所有成員都可以投票,在截止日期之後,有人可以執行該投標。唯一的區別是,這次你必須提供之前提交的相同位元組碼。注意確認視窗上的任何警告:如果它說它不會執行你的程式碼,請檢查截止日期是否已經過去,是否有足夠的投票以及你的交易位元組碼是否已檢出。

讓它更好

以下是當前DAO的一些缺點,我們將其作為練習留給讀者:

  • 你可以將會員列表公開並編入索引嗎?
  • 你能否允許成員改變他們的選票(在投票後但在投票結果出來之前)?
  • 目前投票資訊僅在日誌上可見,你是否可以建立一個顯示所有投票的功能?

======================================================================

分享一些以太坊、EOS、比特幣等區塊鏈相關的互動式線上程式設計實戰教程:

  • java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
  • python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
  • php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
  • 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
  • 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
  • EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
  • java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
  • php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
  • tendermint區塊鏈開發詳解,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI介面、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操程式碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。

匯智網原創翻譯,轉載請標明出處。這裡是原文以太坊DAO之區塊鏈大會

附程式碼:

pragma solidity >=0.4.22 <0.6.0;

contract owned {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) onlyOwner  public {
        owner = newOwner;
    }
}

contract tokenRecipient {
    event receivedEther(address sender, uint amount);
    event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData);

    function receiveApproval(address _from, uint256 _value, address _token, bytes memory _extraData) public {
        Token t = Token(_token);
        require(t.transferFrom(_from, address(this), _value));
        emit receivedTokens(_from, _value, _token, _extraData);
    }

    function () payable external {
        emit receivedEther(msg.sender, msg.value);
    }
}

interface Token {
    function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
}

contract Congress is owned, tokenRecipient {
    // Contract Variables and events
    uint public minimumQuorum;
    uint public debatingPeriodInMinutes;
    int public majorityMargin;
    Proposal[] public proposals;
    uint public numProposals;
    mapping (address => uint) public memberId;
    Member[] public members;

    event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
    event Voted(uint proposalID, bool position, address voter, string justification);
    event ProposalTallied(uint proposalID, int result, uint quorum, bool active);
    event MembershipChanged(address member, bool isMember);
    event ChangeOfRules(uint newMinimumQuorum, uint newDebatingPeriodInMinutes, int newMajorityMargin);

    struct Proposal {
        address recipient;
        uint amount;
        string description;
        uint minExecutionDate;
        bool executed;
        bool proposalPassed;
        uint numberOfVotes;
        int currentResult;
        bytes32 proposalHash;
        Vote[] votes;
        mapping (address => bool) voted;
    }

    struct Member {
        address member;
        string name;
        uint memberSince;
    }

    struct Vote {
        bool inSupport;
        address voter;
        string justification;
    }

    // Modifier that allows only shareholders to vote and create new proposals
    modifier onlyMembers {
        require(memberId[msg.sender] != 0);
        _;
    }

    /**
     * Constructor
     */
    constructor (
        uint minimumQuorumForProposals,
        uint minutesForDebate,
        int marginOfVotesForMajority
    )  payable public {
        changeVotingRules(minimumQuorumForProposals, minutesForDebate, marginOfVotesForMajority);
        // It’s necessary to add an empty first member
        addMember(address(0), "");
        // and let's add the founder, to save a step later
        addMember(owner, 'founder');
    }

    /**
     * Add member
     *
     * Make `targetMember` a member named `memberName`
     *
     * @param targetMember ethereum address to be added
     * @param memberName public name for that member
     */
    function addMember(address targetMember, string memory memberName) onlyOwner public {
        uint id = memberId[targetMember];
        if (id == 0) {
            memberId[targetMember] = members.length;
            id = members.length++;
        }

        members[id] = Member({member: targetMember, memberSince: now, name: memberName});
        emit MembershipChanged(targetMember, true);
    }

    /**
     * Remove member
     *
     * @notice Remove membership from `targetMember`
     *
     * @param targetMember ethereum address to be removed
     */
    function removeMember(address targetMember) onlyOwner public {
        require(memberId[targetMember] != 0);

        for (uint i = memberId[targetMember]; i<members.length-1; i++){
            members[i] = members[i+1];
            memberId[members[i].member] = i;
        }
        memberId[targetMember] = 0;
        delete members[members.length-1];
        members.length--;
    }

    /**
     * Change voting rules
     *
     * Make so that proposals need to be discussed for at least `minutesForDebate/60` hours,
     * have at least `minimumQuorumForProposals` votes, and have 50% + `marginOfVotesForMajority` votes to be executed
     *
     * @param minimumQuorumForProposals how many members must vote on a proposal for it to be executed
     * @param minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed
     * @param marginOfVotesForMajority the proposal needs to have 50% plus this number
     */
    function changeVotingRules(
        uint minimumQuorumForProposals,
        uint minutesForDebate,
        int marginOfVotesForMajority
    ) onlyOwner public {
        minimumQuorum = minimumQuorumForProposals;
        debatingPeriodInMinutes = minutesForDebate;
        majorityMargin = marginOfVotesForMajority;

        emit ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, majorityMargin);
    }

    /**
     * Add Proposal
     *
     * Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     *
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send, in wei
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposal(
        address beneficiary,
        uint weiAmount,
        string memory jobDescription,
        bytes memory transactionBytecode
    )
        onlyMembers public
        returns (uint proposalID)
    {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = beneficiary;
        p.amount = weiAmount;
        p.description = jobDescription;
        p.proposalHash = keccak256(abi.encodePacked(beneficiary, weiAmount, transactionBytecode));
        p.minExecutionDate = now + debatingPeriodInMinutes * 1 minutes;
        p.executed = false;
        p.proposalPassed = false;
        p.numberOfVotes = 0;
        emit ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID+1;

        return proposalID;
    }

    /**
     * Add proposal in Ether
     *
     * Propose to send `etherAmount` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     * This is a convenience function to use if the amount to be given is in round number of ether units.
     *
     * @param beneficiary who to send the ether to
     * @param etherAmount amount of ether to send
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposalInEther(
        address beneficiary,
        uint etherAmount,
        string memory jobDescription,
        bytes memory transactionBytecode
    )
        onlyMembers public
        returns (uint proposalID)
    {
        return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode);
    }

    /**
     * Check if a proposal code matches
     *
     * @param proposalNumber ID number of the proposal to query
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send
     * @param transactionBytecode bytecode of transaction
     */
    function checkProposalCode(
        uint proposalNumber,
        address beneficiary,
        uint weiAmount,
        bytes memory transactionBytecode
    )
        view public
        returns (bool codeChecksOut)
    {
        Proposal storage p = proposals[proposalNumber];
        return p.proposalHash == keccak256(abi.encodePacked(beneficiary, weiAmount, transactionBytecode));
    }

    /**
     * Log a vote for a proposal
     *
     * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
     *
     * @param proposalNumber number of proposal
     * @param supportsProposal either in favor or against it
     * @param justificationText optional justification text
     */
    function vote(
        uint proposalNumber,
        bool supportsProposal,
        string memory justificationText
    )
        onlyMembers public
        returns (uint voteID)
    {
        Proposal storage p = proposals[proposalNumber]; // Get the proposal
        require(!p.voted[msg.sender]);                  // If has already voted, cancel
        p.voted[msg.sender] = true;                     // Set this voter as having voted
        p.numberOfVotes++;                              // Increase the number of votes
        if (supportsProposal) {                         // If they support the proposal
            p.currentResult++;                          // Increase score
        } else {                                        // If they don't
            p.currentResult--;                          // Decrease the score
        }

        // Create a log of this event
        emit Voted(proposalNumber,  supportsProposal, msg.sender, justificationText);
        return p.numberOfVotes;
    }

    /**
     * Finish vote
     *
     * Count the votes proposal #`proposalNumber` and execute it if approved
     *
     * @param proposalNumber proposal number
     * @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it
     */
    function executeProposal(uint proposalNumber, bytes memory transactionBytecode) public {
        Proposal storage p = proposals[proposalNumber];

        require(now > p.minExecutionDate                                            // If it is past the voting deadline
            && !p.executed                                                         // and it has not already been executed
            && p.proposalHash == keccak256(abi.encodePacked(p.recipient, p.amount, transactionBytecode))  // and the supplied code matches the proposal
            && p.numberOfVotes >= minimumQuorum);                                  // and a minimum quorum has been reached...

        // ...then execute result

        if (p.currentResult > majorityMargin) {
            // Proposal passed; execute the transaction

            p.executed = true; // Avoid recursive calling
            
            (bool success, ) = p.recipient.call.value(p.amount)(transactionBytecode);
            require(success);

            p.proposalPassed = true;
        } else {
            // Proposal failed
            p.proposalPassed = false;
        }

        // Fire Events
        emit ProposalTallied(proposalNumber, p.currentResult, p.numberOfVotes, p.proposalPassed);
    }
}