什麼是以太坊DAO?(一)

Decentralized Autonomous Organization,簡稱DAO,以太坊中重要的概念。通常翻譯爲去中心化的自治組織。php

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

到目前爲止,咱們列出的全部合約都是由人類持有的其餘帳戶擁有和執行的。可是在以太坊生態系統中不存在對機器人或人類的歧視,合約能夠像任何其餘賬戶同樣創造任意行爲。合約能夠擁有代幣,參與衆籌,甚至是其餘合約的投票成員。node

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

這種特殊民主的運做方式是它擁有一個像管理員,首席執行官或總統同樣工做的全部者Owner。全部者能夠向組織添加(或刪除)投票成員。任何成員均可以提出一個提議,該提議以以太坊交易的形式發送以太或執行某些合約,其餘成員能夠投票支持或反對該提案。一旦預約的時間量和必定數量的成員投票,就能夠執行提案:合約計票,若是有足夠的票數,它將執行給定的交易。android

區塊鏈大會

文末附所有代碼。程序員

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 ,你將看到設置變量。web

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

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

幾秒鐘後,你將被帶到儀表板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);
    }
}
相關文章
相關標籤/搜索