以太坊DAO之時(shí)間鎖定Multisig

Decentralized Autonomous Organization,簡(jiǎn)稱(chēng)DAO,以太坊中重要的概念。一般翻譯為去中心化的自治組織。

有時(shí)候,時(shí)間也可以用作一種很好的安全機(jī)制。以下代碼基于DAO區(qū)塊鏈大會(huì),但有不同的變化。不是每個(gè)操作需要X個(gè)成員批準(zhǔn),而是任何交易都可以由單個(gè)成員發(fā)起,但它們?cè)趫?zhí)行之前都需要最少的延遲,這取決于交易的支持。提案的批準(zhǔn)越多,就越早執(zhí)行。會(huì)員可以對(duì)交易進(jìn)行投票,這意味著它將取消其他一個(gè)已批準(zhǔn)的簽名。

時(shí)間鎖定Multisig

這意味著如果你沒(méi)有緊急程度,則執(zhí)行任何交易可能只需要一個(gè)或兩個(gè)簽名。但是,如果單個(gè)密鑰被泄露,其他密鑰可以將該交易延遲數(shù)月或數(shù)年,甚至可以阻止其執(zhí)行。

這個(gè)怎么運(yùn)作

所有密鑰都已批準(zhǔn)的交易可以在十分鐘后執(zhí)行(此金額是可配置的),并且每5%未投票的成員每次需要的時(shí)間加倍(如果他們主動(dòng)投票,則為四倍)反對(duì))。如果它是一個(gè)簡(jiǎn)單的ether交易,只要支持投票將其置于所需的時(shí)間內(nèi),就會(huì)執(zhí)行交易,但更復(fù)雜的交易將要求使用正確的字節(jié)碼手動(dòng)執(zhí)行交易。這些是默認(rèn)值,但在創(chuàng)建合約時(shí)可以設(shè)置不同的值:

批準(zhǔn)交易的成員數(shù)量:近似時(shí)間延遲

  • 100%批準(zhǔn):10分鐘(最低默認(rèn)值)
  • 90%批準(zhǔn):40分鐘
  • 80%:2小時(shí)40分鐘
  • 50%:大約一周
  • 40%:1個(gè)月
  • 30%:4個(gè)月
  • 20%:超過(guò)一年
  • 10%或更少:5年或從不
    一旦最短的時(shí)間過(guò)去,任何人都可以執(zhí)行交易(參見(jiàn)“國(guó)會(huì)”以獲得更完整的步行)。這是故意的,因?yàn)樗试S某人安排交易或雇用其他人來(lái)執(zhí)行交易。

代碼:

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 TimeLockMultisig is owned, tokenRecipient {

    Proposal[] public proposals;
    uint public numProposals;
    mapping (address => uint) public memberId;
    Member[] public members;
    uint minimumTime = 10;

    event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
    event Voted(uint proposalID, bool position, address voter, string justification);
    event ProposalExecuted(uint proposalID, int result, uint deadline);
    event MembershipChanged(address member, bool isMember);

    struct Proposal {
        address recipient;
        uint amount;
        string description;
        bool executed;
        int currentResult;
        bytes32 proposalHash;
        uint creationDate;
        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
     *
     * First time setup
     */
    constructor(
        address founder, 
        address[] memory initialMembers, 
        uint minimumAmountOfMinutes
    ) payable public {
        if (founder != address(0)) owner = founder;
        if (minimumAmountOfMinutes !=0) minimumTime = minimumAmountOfMinutes;
        // 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');
        changeMembers(initialMembers, true);
    }

    /**
     * Add member
     *
     * @param targetMember address to add as a member
     * @param memberName label to give this member address
     */
    function addMember(address targetMember, string memory memberName) onlyOwner public
    {
        uint id;
        if (memberId[targetMember] == 0) {
            memberId[targetMember] = members.length;
            id = members.length++;
        } else {
            id = memberId[targetMember];
        }

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

    /**
     * Remove member
     *
     * @param targetMember the member to remove
     */
    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--;
    }

    /**
     * Edit existing members
     *
     * @param newMembers array of addresses to update
     * @param canVote new voting value that all the values should be set to
     */
    function changeMembers(address[] memory newMembers, bool canVote) public {
        for (uint i = 0; i < newMembers.length; i++) {
            if (canVote)
                addMember(newMembers[i], '');
            else
                removeMember(newMembers[i]);
        }
    }

    /**
     * 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.executed = false;
        p.creationDate = now;
        emit ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID+1;
        vote(proposalID, true, '');

        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
    {
        Proposal storage p = proposals[proposalNumber]; // Get the proposal
        require(p.voted[msg.sender] != true);           // If has already voted, cancel
        p.voted[msg.sender] = true;                     // Set this voter as having voted
        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);

        // If you can execute it now, do it
        if ( now > proposalDeadline(proposalNumber)
            && p.currentResult > 0
            && p.proposalHash == keccak256(abi.encodePacked(p.recipient, p.amount, ''))
            && supportsProposal) {
            executeProposal(proposalNumber, '');
        }
    }

    function proposalDeadline(uint proposalNumber) public view returns(uint deadline) {
        Proposal storage p = proposals[proposalNumber];
        uint factor = calculateFactor(uint(p.currentResult), (members.length - 1));
        return p.creationDate + uint(factor * minimumTime *  1 minutes);
    }

    function calculateFactor(uint a, uint b) public pure returns (uint factor) {
        return 2**(20 - (20 * a)/b);
    }

    /**
     * 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 >= proposalDeadline(proposalNumber)                                         // If it is past the voting deadline
            && p.currentResult > 0                                                              // and a minimum quorum has been reached
            && !p.executed                                                                      // and it is not currently being executed
            && checkProposalCode(proposalNumber, p.recipient, p.amount, transactionBytecode));  // and the supplied code matches the proposal...


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

        // Fire Events
        emit ProposalExecuted(proposalNumber, p.currentResult, proposalDeadline(proposalNumber));
    }
}

部署和使用

像以前一樣在這些教程上部署該代碼。在部署參數(shù)上,將最小時(shí)間留空將默認(rèn)為30分鐘,如果你想要更快的鎖定時(shí)間,則放1分鐘。上傳后,執(zhí)行“添加成員”功能以添加組的新成員,他們可以是你認(rèn)識(shí)的其他人,也可以是不同計(jì)算機(jī)上的帳戶(hù)或離線存儲(chǔ)。

設(shè)置為所有者owner的帳戶(hù)非常強(qiáng)大,因?yàn)樗梢噪S意添加或刪除成員。因此,在添加主成員后,我們建議你通過(guò)執(zhí)行Transfer Membership功能將owner設(shè)置為另一個(gè)帳戶(hù)。如果你希望對(duì)所有成員的添加或刪除進(jìn)行投票,則將其設(shè)置為multisig本身,就像任何其他交易一樣。另一種方法是將其設(shè)置為另一個(gè)受信任的multisig錢(qián)包,如果你希望永久修復(fù)成員數(shù),則可以設(shè)置為0x000。請(qǐng)記住,此合約上的資金僅與“所有者”帳戶(hù)一樣安全。

與上述任何DAO一樣,此合約可以持有以太幣,任何基于以太坊的代幣并執(zhí)行任何合約。為此,請(qǐng)檢查如何在國(guó)會(huì)DAO上執(zhí)行復(fù)雜的提案。

警告和改進(jìn)

為簡(jiǎn)單起見(jiàn),對(duì)提案的投票僅僅算得少一點(diǎn)支持。如果你愿意,你可以玩弄負(fù)面投票更重要的想法,但這意味著少數(shù)成員可以對(duì)任何提議的交易擁有有效的否決權(quán)!

你怎么能改善這個(gè)合約?

我們?nèi)ヌ剿靼桑?/h3>

你已經(jīng)到了本教程的末尾,但這只是一次偉大冒險(xiǎn)的開(kāi)始?;仡櫼幌?,看看你取得了多少成就:你創(chuàng)造了一個(gè)活生生的,有說(shuō)服力的機(jī)器人,你自己的加密貨幣,通過(guò)無(wú)信息的眾籌籌集資金,并用它來(lái)啟動(dòng)你自己的個(gè)人民主組織。

接下來(lái)會(huì)發(fā)生什么?

  • 你仍然控制的代幣可以在分散的交易所出售,或者交易商品和服務(wù),以資助第一份合約的進(jìn)一步發(fā)展和發(fā)展組織。
  • 你的DAO可以在名稱(chēng)注冊(cè)商處擁有自己的名稱(chēng),然后更改它重定向的位置,以便在代幣持有者批準(zhǔn)時(shí)自行更新。
  • 該組織不僅可以擁有醚類(lèi),還可以擁有在以太坊上創(chuàng)造的任何其他類(lèi)型的硬幣,包括其價(jià)值與比特幣或美元相關(guān)的資產(chǎn)。
  • 可以對(duì)DAO進(jìn)行編程,以允許具有多個(gè)交易的提案,其中一些預(yù)定在未來(lái)。它還可以擁有其他DAO的股份,這意味著它可以對(duì)更大的組織投票或成為DAO聯(lián)盟的一部分。
  • 代幣合約可以重新編程為持有以太或持有其他代幣并將其分發(fā)給代幣持有者。這會(huì)將代幣的價(jià)值與其他資產(chǎn)的價(jià)值聯(lián)系起來(lái),因此只需將資金轉(zhuǎn)移到代幣地址即可實(shí)現(xiàn)支付股息。

這一切都意味著你創(chuàng)造的這個(gè)小社會(huì)可以成長(zhǎng),從第三方獲得資金,支付經(jīng)常性工資,擁有任何類(lèi)型的加密資產(chǎn),甚至使用眾籌為其活動(dòng)提供資金。所有這些都具有完全透明,完全的問(wèn)責(zé)制和完全免受任何人為干擾。當(dāng)網(wǎng)絡(luò)存在時(shí),合約將完全執(zhí)行它們被創(chuàng)建的代碼,而沒(méi)有任何例外,永遠(yuǎn)執(zhí)行。

那么你的合約是什么?它會(huì)是一個(gè)國(guó)家,一個(gè)公司,一個(gè)非營(yíng)利組織嗎?你的代碼會(huì)做什么?

隨你,由你決定。

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

分享一些以太坊、EOS、比特幣等區(qū)塊鏈相關(guān)的交互式在線編程實(shí)戰(zhàn)教程:

  • java以太坊開(kāi)發(fā)教程,主要是針對(duì)java和android程序員進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的web3j詳解。
  • python以太坊,主要是針對(duì)python工程師使用web3.py進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的詳解。
  • php以太坊,主要是介紹使用php進(jìn)行智能合約開(kāi)發(fā)交互,進(jìn)行賬號(hào)創(chuàng)建、交易、轉(zhuǎn)賬、代幣開(kāi)發(fā)以及過(guò)濾器和交易等內(nèi)容。
  • 以太坊入門(mén)教程,主要介紹智能合約與dapp應(yīng)用開(kāi)發(fā),適合入門(mén)。
  • 以太坊開(kāi)發(fā)進(jìn)階教程,主要是介紹使用node.js、mongodb、區(qū)塊鏈、ipfs實(shí)現(xiàn)去中心化電商DApp實(shí)戰(zhàn),適合進(jìn)階。
  • C#以太坊,主要講解如何使用C#開(kāi)發(fā)基于.Net的以太坊應(yīng)用,包括賬戶(hù)管理、狀態(tài)與交易、智能合約開(kāi)發(fā)與交互、過(guò)濾器和交易等。
  • EOS教程,本課程幫助你快速入門(mén)EOS區(qū)塊鏈去中心化應(yīng)用的開(kāi)發(fā),內(nèi)容涵蓋EOS工具鏈、賬戶(hù)與錢(qián)包、發(fā)行代幣、智能合約開(kāi)發(fā)與部署、使用代碼與智能合約交互等核心知識(shí)點(diǎn),最后綜合運(yùn)用各知識(shí)點(diǎn)完成一個(gè)便簽DApp的開(kāi)發(fā)。
  • java比特幣開(kāi)發(fā)教程,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念,例如區(qū)塊鏈存儲(chǔ)、去中心化共識(shí)機(jī)制、密鑰與腳本、交易與UTXO等,同時(shí)也詳細(xì)講解如何在Java代碼中集成比特幣支持功能,例如創(chuàng)建地址、管理錢(qián)包、構(gòu)造裸交易等,是Java工程師不可多得的比特幣開(kāi)發(fā)學(xué)習(xí)課程。
  • php比特幣開(kāi)發(fā)教程,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念,例如區(qū)塊鏈存儲(chǔ)、去中心化共識(shí)機(jī)制、密鑰與腳本、交易與UTXO等,同時(shí)也詳細(xì)講解如何在Php代碼中集成比特幣支持功能,例如創(chuàng)建地址、管理錢(qián)包、構(gòu)造裸交易等,是Php工程師不可多得的比特幣開(kāi)發(fā)學(xué)習(xí)課程。
  • tendermint區(qū)塊鏈開(kāi)發(fā)詳解,本課程適合希望使用tendermint進(jìn)行區(qū)塊鏈開(kāi)發(fā)的工程師,課程內(nèi)容即包括tendermint應(yīng)用開(kāi)發(fā)模型中的核心概念,例如ABCI接口、默克爾樹(shù)、多版本狀態(tài)庫(kù)等,也包括代幣發(fā)行等豐富的實(shí)操代碼,是go語(yǔ)言工程師快速入門(mén)區(qū)塊鏈開(kāi)發(fā)的最佳選擇。

匯智網(wǎng)原創(chuàng)翻譯,轉(zhuǎn)載請(qǐng)標(biāo)明出處。這里是原文以太坊DAO之時(shí)間鎖定的Multisig

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容