以太坊DAO之時間鎖定Multisig
Decentralized Autonomous Organization,簡稱DAO,以太坊中重要的概念。一般翻譯為去中心化的自治組織。
有時候,時間也可以用作一種很好的安全機制。以下程式碼基於DAO區塊鏈大會,但有不同的變化。不是每個操作需要X個成員批准,而是任何交易都可以由單個成員發起,但它們在執行之前都需要最少的延遲,這取決於交易的支援。提案的批准越多,就越早執行。會員可以對交易進行投票,這意味著它將取消其他一個已批准的簽名。
時間鎖定Multisig
這意味著如果你沒有緊急程度,則執行任何交易可能只需要一個或兩個簽名。但是,如果單個金鑰被洩露,其他金鑰可以將該交易延遲數月或數年,甚至可以阻止其執行。
這個怎麼運作
所有金鑰都已批准的交易可以在十分鐘後執行(此金額是可配置的),並且每5%未投票的成員每次需要的時間加倍(如果他們主動投票,則為四倍)反對)。如果它是一個簡單的ether交易,只要支援投票將其置於所需的時間內,就會執行交易,但更復雜的交易將要求使用正確的位元組碼手動執行交易。這些是預設值,但在建立合約時可以設定不同的值:
批准交易的成員數量:近似時間延遲
- 100%批准:10分鐘(最低預設值)
- 90%批准:40分鐘
- 80%:2小時40分鐘
- 50%:大約一週
- 40%:1個月
- 30%:4個月
- 20%:超過一年
- 10%或更少:5年或從不 一旦最短的時間過去,任何人都可以執行交易(參見“國會”以獲得更完整的步行)。這是故意的,因為它允許某人安排交易或僱用其他人來執行交易。
程式碼:
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)); } }
部署和使用
像以前一樣在這些教程上部署該程式碼。在部署引數上,將最小時間留空將預設為30分鐘,如果你想要更快的鎖定時間,則放1分鐘。上傳後,執行“新增成員”功能以新增組的新成員,他們可以是你認識的其他人,也可以是不同計算機上的帳戶或離線儲存。
設定為所有者owner的帳戶非常強大,因為它可以隨意新增或刪除成員。因此,在新增主成員後,我們建議你通過執行Transfer Membership
功能將owner
設定為另一個帳戶。如果你希望對所有成員的新增或刪除進行投票,則將其設定為multisig本身,就像任何其他交易一樣。另一種方法是將其設定為另一個受信任的multisig
錢包,如果你希望永久修復成員數,則可以設定為0x000。請記住,此合約上的資金僅與“所有者”帳戶一樣安全。
與上述任何DAO一樣,此合約可以持有以太幣,任何基於以太坊的代幣並執行任何合約。為此,請檢查如何在國會DAO上執行復雜的提案。
警告和改進
為簡單起見,對提案的投票僅僅算得少一點支援。如果你願意,你可以玩弄負面投票更重要的想法,但這意味著少數成員可以對任何提議的交易擁有有效的否決權!
你怎麼能改善這個合約?
我們去探索吧!
你已經到了本教程的末尾,但這只是一次偉大冒險的開始。回顧一下,看看你取得了多少成就:你創造了一個活生生的,有說服力的機器人,你自己的加密貨幣,通過無資訊的眾籌籌集資金,並用它來啟動你自己的個人民主組織。
接下來會發生什麼?
- 你仍然控制的代幣可以在分散的交易所出售,或者交易商品和服務,以資助第一份合約的進一步發展和發展組織。
- 你的DAO可以在名稱註冊商處擁有自己的名稱,然後更改它重定向的位置,以便在代幣持有者批准時自行更新。
- 該組織不僅可以擁有醚類,還可以擁有在以太坊上創造的任何其他型別的硬幣,包括其價值與比特幣或美元相關的資產。
- 可以對DAO進行程式設計,以允許具有多個交易的提案,其中一些預定在未來。它還可以擁有其他DAO的股份,這意味著它可以對更大的組織投票或成為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之時間鎖定的Multisig