以太坊官网教程笔记

以太坊的官网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来共同对发起人的行为进行决策。


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