以太坊官網教程筆記

以太坊的官網https://ethereum.org/上只有4個主要部分:

錢包客戶端下載、token創建、ICO發行、投票系統DAO。

但實際上ICO的發行需要token的創建和投票系統DAO的基礎。

以ICO(在網站上被稱作crowdsale)爲目的,簡要翻譯一下官網內容,具體的圖片就不貼了,可以自行對照官網閱讀。


一、以太坊上的crowdsale(ICO)實現

以太坊在官網文檔中介紹瞭如何使用solidity來實現所謂的“crowdsale”(其實就是ICO)。即在項目正式推出之前銷售token,來爲這個項目的開發籌集資金。買家在crowdsale買了token後,可以把token拿到公開市場上購買和出售,獲得獨立於應用程序自己的市場價值。總的來說,crowdsale就是先在以太坊的框架下,先創建自己的貨幣token,然後通過出售這種token融資。個人認爲以太坊本身也就是一次大型的crowdsale,只不過以太坊成功在衆多山寨幣中脫穎而出,成爲僅此於比特幣的虛擬貨幣,相較其他林林總總的token更讓人放心。

 

和衆籌不一樣的是,crowdsale能夠讓那些投資者很快將持有的token在數字貨幣交易所中進行交易。另外一個巨大的不同點,就是crowdsale的是建立在使用token作爲憑證的基礎上,同時發行者持有自己衆籌項目的一部分股份。總的來說這個所謂的crowdsale還是ICO的一種好聽的說法,我感覺官網的措辭也在刻意迴避ICO這個概念。

 

Crowdsale雖然載體是貨幣,但是核心原則是和傳統意義上的衆籌是一樣的:如果衆籌失敗,資金將會返還給贊助者;如果衆籌成功,資金則交付給衆籌發起人。同時,它應該滿足以下的要求:首先,錯過衆籌deadline的人不能再投資進來;第二,已經投資的人不能退款,但是它可以把自己投資的憑證(token)轉讓給別人。

 

Crowdsale基礎有兩個:①token的發行和②自治組織DAO(其實就是多個人之間,公平地做出決策的系統,或者說投票系統)。Token的發行之前已經學過,具體功能也就是基本的轉賬和發行。而自治組織(DAO,Decentralized Autonomous Organization),最基本的功能就是通過投票決定提案是否通過的過程。

 

1.    token的實現

簡要介紹一下Token發行代碼的功能。

n  設置初始token總量

n  用戶之間的轉賬

n  中心化的用戶資產的管理;通過挖礦難度對幣值的管理。

n  類似股票交易的買入、賣出。

n  PoW的設置。以太坊推薦使用“Casper”來替代PoW,Casper是一種PoS下注機制:要求驗證人對共識結果進行下注。而共識結果又通過驗證人的下注情況形成:驗證人必須猜測其他人會賭哪個塊勝出,同時也下注這個塊。如果賭對了,他們就可以拿回保證金外加交易費用。PoW共識同樣可以理解爲是一個下注機制:礦工選擇一個塊基於它進行挖礦,也就是賭這個塊會成爲主鏈的一部分;如果賭對了,他可以收到獎勵,而如果賭錯了,他會損失電費。

 

2.    自治組織DAO的實現

 

自治系統的執行過程:

n  在一臺機器A上部署好合約,在另外一臺機器B上輸入地址來觀測這個合約。如果A發起了一個提案,比如要轉一筆錢出去,那麼B在自己的客戶端可以選擇同意或者不同意。

n  另外,如果試圖用自己的token來進行交易,需要使用在兩臺機器上驗證命令的hash來實現,因爲原本的命令代碼長度過長,全存到區塊鏈上太浪費。

 

自治系統主要函數的解讀:

pragma solidity ^0.4.16;

 

contract owned {

    address public owner;

 

    function owned()  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 _extraData) public {

        Token t = Token(_token);

        require(t.transferFrom(_from, this, _value));

        receivedTokens(_from, _value, _token, _extraData);

    }

 

    function () payable  public {

        receivedEther(msg.sender, msg.value);

    }

}

 

interface Token {

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);

}

這部分對應自擬貨幣token的部分操作。

 

Owned:

存在一位中心化的管理員。

 

有些函數只有owner才能操作。

 

Owner的權限可以轉移。

 

 

 

 

TokenRecipient:

管理轉賬的過程。

 

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 votingDeadline;

        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);

        _;

    }

Congress合約的變量定義和event。

一個合約相當於一個類。

 

 

 

 

 

這些Event幫助用戶在客戶端中用按鈕的方式來快捷操作。

 

例如投票時只需要在voted菜單下選擇是否同意就可以了。

 

proposal的結構體包含:

提案參與者,

人數,

提案描述,

截止時間

當前票數等等。

 

 

 

 

 

 

Member的結構體包括:

Member的

地址,

名字,

加入時間。

 

 

Vote的結構體包括:

是否支持,

支持者的名字,

Justification是可選項,投票者可以選擇填寫自己投票的原因。

 

 

 

    function Congress (

        uint minimumQuorumForProposals,

        uint minutesForDebate,

        int marginOfVotesForMajority

    )

payable public {

        changeVotingRules(minimumQuorumForProposals, minutesForDebate, marginOfVotesForMajority);

        // It’s necessary to add an empty first member

        addMember(0, "");

        // and let's add the founder, to save a step later

        addMember(owner, 'founder');

    }

 

Congress的構造函數。

 

要定義的包括

提案參與人數,

達成共識所需的人數,

持續時間。,

 

 

 

 

 

      function addMember(address targetMember, string 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});

        MembershipChanged(targetMember, true);

    }

addMember()接受兩個參數:新成員的地址和名字。將他們加到數組裏。

 

removeMember();

刪除成員的函數。

 

 

    function changeVotingRules(

        uint minimumQuorumForProposals,

        uint minutesForDebate,

        int marginOfVotesForMajority

    ) onlyOwner public {

        minimumQuorum = minimumQuorumForProposals;

        debatingPeriodInMinutes = minutesForDebate;

        majorityMargin = marginOfVotesForMajority;

 

        ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, majorityMargin);

    }

 

changeVotingRule改變投票規則。

 

輸入3個參數:

參與表決的人數,

持續時間,

表決通過的目標人數。

 

直接修改即可。

 

    function newProposal(

        address beneficiary,

        uint weiAmount,

        string jobDescription,

        bytes 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(beneficiary, weiAmount, transactionBytecode);

        p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes;

        p.executed = false;

        p.proposalPassed = false;

        p.numberOfVotes = 0;

        ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);

        numProposals = proposalID+1;

 

        return proposalID;

    }

 

newProposal建立一個新提案。

 

內容很簡單:是否要轉移一定數量的以太幣到某個賬戶中去?

 

這個提案的transactionBytecode爲空。

 

    function newProposalInEther();

和上一個函數作用一樣,

只不過這次轉賬的單位是以太幣,

上一個函數的單位是以太幣的最小單位wei。

 

    function checkProposalCode(

        uint proposalNumber,

        address beneficiary,

        uint weiAmount,

        bytes transactionBytecode

    )

        constant public

        returns (bool codeChecksOut)

    {

        Proposal storage p = proposals[proposalNumber];

        return p.proposalHash == keccak256(beneficiary, weiAmount, transactionBytecode);

    }

 

checkProposalCode函數用來對比提案的hash是否相同。

 

    function vote(

        uint proposalNumber,

        bool supportsProposal,

        string 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

        Voted(proposalNumber,  supportsProposal, msg.sender, justificationText);

        return p.numberOfVotes;

    }

 

 

Vote函數接受vote結構體中的三個參數,即

1.       提案代號,

2.       是否支持

3.       支持原因

 

 

 

 

 

 

如果支持的話,那麼總支持人數++。

 

 

可以選擇發送自己的支持(反對)原因。

 

    function executeProposal(uint proposalNumber, bytes transactionBytecode) public {

        Proposal storage p = proposals[proposalNumber];

 

        require(now > p.votingDeadline                                            // If it is past the voting deadline

            && !p.executed                                                         // and it has not already been executed

            && p.proposalHash == keccak256(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

            require(p.recipient.call.value(p.amount)(transactionBytecode));

 

            p.proposalPassed = true;

        } else {

            // Proposal failed

            p.proposalPassed = false;

        }

 

        // Fire Events

        ProposalTallied(proposalNumber, p.currentResult, p.numberOfVotes, p.proposalPassed);

    }

}

executeProposal即最後的唱票環節。

 

如果

1. 時間在ddl之前

2. 沒被強制取消

3. Hash對的上

4. 支持人數夠

 

那麼提案即通過。

 

以上就是一個最基本的投票過程了。實際上還可以做以下改進:

如果把自制token的符號設置爲%,總量設置爲100,那麼就可以用這個權重來表示每個投票者的重要程度,比如按股權分配投票。

l  投票者可以更改自己的投票結果。

l  投票者可以到市場上出售,轉讓自己的投票權。

l  另外建一個contract,用來管理這個contract的owner——這樣就解決了管理者權力太大的問題。

l  還可以把自己的投票權託付給另外一名投票者,他的選擇就代表我的選擇。

l  一個人也可以發起一個提案,但是要把時間作爲能否執行一部分。也就是說,如果投票同意的人數越少,這個提案可能要幾年之後才能通過;但是如果投票人數很多,幾分鐘之後便默認通過。

 

3.    crowdsale的實現

Crowdsale要解決的主要問題:首先如何判斷一個項目是否成功還是失敗?根據之前的投票系統,可以使用統計票數類似的函數來判斷是否達到了衆籌目標,唯一的區別是,之前判斷票數是不是夠數,這次是判斷錢到不到數。第二個問題是資金如何管理?要有監督者對已經籌集到的錢進行監督。這個監督者在之前是各個投票者,現在就是每個投資者,根據每個人的股份不同投票的佔比不同。

 

首先,根據前面的兩步創建一個token和一個DAO。Token的總量爲100個,DAO提案的目標需要10個投票。Token的作用是給參與者憑證——當然token本身也可以作爲獎勵。通過DAO,衆籌參與的過程其實就等同於向提案投票的過程。

 

之後就可以直接copy代碼建立合約了。

 

 pragma solidity ^0.4.16;

 

interface token {

    function transfer(address receiver, uint amount);

}

Interface是一段提示contract內容的語句 。

 

 

  contract Crowdsale {

    address public beneficiary;

    uint public fundingGoal;

    uint public amountRaised;

    uint public deadline;

    uint public price;

    token public tokenReward;

    mapping(address => uint256) public balanceOf;

    bool fundingGoalReached = false;

    bool crowdsaleClosed = false;

 

    event GoalReached(address recipient, uint totalAmountRaised);

    event FundTransfer(address backer, uint amount, bool isContribution);

 

    modifier afterDeadline() { if (now >= deadline) _;

 }

 

合約crowdsale。

 

主要變量包括:

衆籌目標,

已籌得的錢,

DDL,

Token獎勵等。

 

    function Crowdsale(

        address ifSuccessfulSendTo,

        uint fundingGoalInEthers,

        uint durationInMinutes,

        uint etherCostOfEachToken,

        address addressOfTokenUsedAsReward

    ) {

        beneficiary = ifSuccessfulSendTo;

        fundingGoal = fundingGoalInEthers * 1 ether;

        deadline = now + durationInMinutes * 1 minutes;

        price = etherCostOfEachToken * 1 ether;

        tokenReward = token(addressOfTokenUsedAsReward);

    }

 

 

構造函數。接受四個參數:

1.       收款人,即如果衆籌成功,收款人的地址。

2.       截止時間。

3.       每個token的售價。

4.       作爲獎勵的Token地址。

 

注意這其中的單位,這些單位都是編程語言預先定義好的神奇變量。

 

 

    function () payable {

        require(!crowdsaleClosed);

        uint amount = msg.value;

        balanceOf[msg.sender] += amount;

        amountRaised += amount;

        tokenReward.transfer(msg.sender, amount / price);

        FundTransfer(msg.sender, amount, true);

    }

 

    modifier afterDeadline() { if (now >= deadline) _; }

 

 

這個沒有名字的函數是一個默認函數,每當有人來送錢就調用這個函數。

 

功能就是把送來的錢加到總額裏。

 

    function checkGoalReached() afterDeadline {

        if (amountRaised >= fundingGoal){

            fundingGoalReached = true;

            GoalReached(beneficiary, amountRaised);

        }

        crowdsaleClosed = true;

    }

 

checkGoalReached()檢測有沒有達到衆籌目標。如果達到了,關閉衆籌。

 

 

    function safeWithdrawal() afterDeadline {

        if (!fundingGoalReached) {

            uint amount = balanceOf[msg.sender];

            balanceOf[msg.sender] = 0;

            if (amount > 0) {

                if (msg.sender.send(amount)) {

                    FundTransfer(msg.sender, amount, false);

                } else {

                    balanceOf[msg.sender] = amount;

                }

            }

        }

 

 

        if (fundingGoalReached && beneficiary == msg.sender) {

            if (beneficiary.send(amountRaised)) {

                FundTransfer(beneficiary, amountRaised, false);

            } else {

                //If we fail to send the funds to beneficiary, unlock funders balance

                fundingGoalReached = false;

            }

        }

    }

}

safeWithdrawal()

如果目標沒能達成,那麼所有的款項都將退回資助者的賬戶中。

 

 

 

 

 

 

 

 

如果目標達成,但是錢無法打到發起人賬戶中,結果同樣是退回資助者手中。

 

這就建立了最基本的衆籌系統,此外還要進行一些改進:

l  可以在達到衆籌目標之後不停止籌款,讓更多想加入的人加入。

l  調整無名函數,這個函數每當有人來送錢的時候就自動調用一次,調整之後的函數通過監視捐款額來防止51%攻擊。

l  根據以太幣的block號而非自然時間來確定衆籌的ddl。由於以太坊大約17秒產生一個區塊,使用公式current_block_number+ duration_in_minutes * 60 / 17 + buffer來確定截止塊號。

衆籌成功之後,所有的參與者可以通過DAO來共同對發起人的行爲進行決策。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章