Go-Ethereum 1.7.2 结合 Mist 0.9.2 实现众筹合约的实例

目录

1、什么是ICO?

  ICO是以初始产生的数字加密货币作为投资回报的一种筹措资金的方式,它的概念源自证券界的Initial Public Offering(IPO,首次公开发行)。

  相较于传统意义上的IPO,ICO具有可以缩短投融资链、降低投融资门槛、流动性佳、全球性投资等优势。常见的ICO里,数字货币和区块链项目向早期爱好者出售项目代币。项目团队通过ICO获取技术开发和市场拓展资金;而项目爱好者通过ICO支持项目,同时也可在对应代币进入交易市场后选择交易退出。

  当你有一个好的想法,需要大家的资金来资助你。你可以使用众筹合约来发起请求捐款。众筹合约的基本思路是,你设定一个众筹目标,在达到目标的最后期限时,如果没有完成众筹,所有的捐款将被退回,因此减少了捐赠者的风险。由于代码是开放的,可被审计的,也就不需要一个集中的、可信的平台来担保,每个捐款的人,只需要支付一定的gas

2、众筹的奖励-代币

  一般来说,那些筹集资金的人在资金筹集和资金管理不善之后,根本就不能说这笔钱是如何使用的,这常常导致项目根本无法交付任何东西。这时我们可以使用智能合适中投票的方式来做决定,这样对所有人都是公平的。(这个例子不在本文中介绍,可以参考链接)

  在下面的例子里,我们在众筹中,主要解决两个重要的问题:如何管理和保存用于奖励的代币;筹集奖金后如何使用。

  传统的众筹或奖励记录通常有一个中央数据库,来保存、跟踪所有捐助者的过程:谁错过了众筹的最后期限了,谁在众筹过程中捐赠了多少等。与之相反,在区块链中我们将以分散的方式来做这件事,只需创建一个标记来记录众筹的每一条记录、奖励了多少代币,后面每个捐赠者都可以得到一个他们可以交易、出售或保留的代币。如果要给予实物奖励,生产者只需要交换实物产品的代币。捐赠者也可以将代币做为纪念品保留,不管这个众筹项目有没有达到它的目标,都可以收藏。

3、众筹合约的完善

3.1、设置众筹合约中使用的代币

  下面是一段简单的 代币代码,用于发行给捐赠者,注意我们没有设置代币的总量,而是一直在增发,在实际使用过程中,可以根据需求自行做限制:

pragma solidity 0.4.16;
/**
 * 一个简单的代币合约。
 */
contract token {

    string public standard = 'https://mshk.top';
    string public name; //代币名称
    string public symbol; //代币符号比如'$'
    uint8 public decimals = 2;  //代币单位,展示的小数点后面多少个0,和以太币一样后面是是18个0
    uint256 public totalSupply; //代币总量
    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;

    event Transfer(address indexed from, address indexed to, uint256 value);  //转帐通知事件


    /* 初始化合约,并且把初始的所有代币都给这合约的创建者
     * @param _owned 合约的管理者
     * @param tokenName 代币名称
     * @param tokenSymbol 代币符号
     */
    function token(address _owned, string tokenName, string tokenSymbol) {
        //合约的管理者获得的代币总量
        balanceOf[_owned] = totalSupply;

        name = tokenName;
        symbol = tokenSymbol;

    }

    /**
     * 转帐,具体可以根据自己的需求来实现
     * @param  _to address 接受代币的地址
     * @param  _value uint256 接受代币的数量
     */
    function transfer(address _to, uint256 _value){
      //从发送者减掉发送额
      balanceOf[msg.sender] -= _value;

      //给接收者加上相同的量
      balanceOf[_to] += _value;

      //通知任何监听该交易的客户端
      Transfer(msg.sender, _to, _value);
    }

    /**
     * 增加代币,并将代币发送给捐赠新用户
     * @param  _to address 接受代币的地址
     * @param  _amount uint256 接受代币的数量
     */
    function issue(address _to, uint256 _amount) public{
        totalSupply = totalSupply + _amount;
        balanceOf[_to] += _amount;

        //通知任何监听该交易的客户端
        Transfer(this, _to, _amount);
    }
 }

  
  上面的代码,在Mist中是执行的效果如下:

mshk.top

  

3.2、众筹合约的基本设置

  
  在众筹合约中,下面几个变量可以用于设置众筹以太币总量、众筹截止时间、以太币和代币的兑换比例,如果不使用单位进行声明换算,默认在以太坊中,所有的单位都是wei1 ether=10^18 wei

fundingGoal = fundingGoalInEthers * 1 ether;  //众筹以太币总量
deadline = now + durationInMinutes * 1 minutes; //众筹截止时间,单位是分钟
price = 500 finney; //1个以太币可以买 2 个代币

  
  在初始化众筹合约构造函数的时候,我们会将众筹合约的帐户地址,传递给代币做为管理地址,这里使用的是关键字this表示当前合约的地址,也可以传递给某个人,初始创建时奖励给这个人指定量的代币。

function Crowdsale(
    uint fundingGoalInEthers,
    uint durationInMinutes,
    string tokenName,
    string tokenSymbol
) token(this, tokenName, tokenSymbol)

  

3.3、让众筹合约接收以太币

  在 solidity 中,有一个未命名函数当合约收到以太币的时候会默认执行。我们加上 payable 关键字,就可以给这个合约打款(存入以太币),然后我们可以根据之前的以太币与代币的兑换比例,奖励给某人一定量的代币,并进行统计。

/**
 * 默认函数
 *
 * 默认函数,可以向合约直接打款
 */
function () payable {

    //判断是否关闭众筹
    require(!crowdsaleClosed);
    uint amount = msg.value;

    //捐款人的金额累加
    balance[msg.sender] += amount;

    //捐款总额累加
    amountRaised += amount;

    //转帐操作,转多少代币给捐款人
    issue(msg.sender, amount / price * 10 ** uint256(decimals));
    FundTransfer(msg.sender, amount, true);
}

  
  上面的代码中如果众筹合约关闭(crowdsaleClosed = true),则不继续执行,无法交易,这样做的原因是,如果众筹合约无论是成功或是已经结束,避免其他人赔钱打款,造成后期的不必要纠纷。

mshk.top

  

3.4、检测众筹合约是否完成

  检测众筹合约是否达到的代码如下,如果已筹集的资金额,达到了众筹的目标值,则结束众筹,并通知客户端做记录

/**
 * 检测众筹目标是否已经达到
 */
function checkGoalReached() afterDeadline {
    if (amountRaised >= fundingGoal){
        //达成众筹目标
        fundingGoalReached = true;
        GoalReached(beneficiary, amountRaised);
    }

    //关闭众筹
    crowdsaleClosed = true;
}

  

3.5、众筹结束后的操作

  同时我们还提供了另一个方法,当众筹失败的时候,捐赠者可以取回自己的捐助,如果众筹成功,受益人可以获得众筹到的以太币。

/**
 * 收回资金
 *
 * 检查是否达到了目标或时间限制,如果有,并且达到了资金目标,
 * 将全部金额发送给受益人。如果没有达到目标,每个贡献者都可以退出
 * 他们贡献的金额
 */
function safeWithdrawal() afterDeadline {

    //如果没有达成众筹目标
    if (!fundingGoalReached) {
        //获取合约调用者已捐款余额
        uint amount = balance[msg.sender];

        if (amount > 0) {
            //返回合约发起者所有余额
            msg.sender.transfer(amount);
            FundTransfer(msg.sender, amount, false);
            balance[msg.sender] = 0;
        }
    }

    //如果达成众筹目标,并且合约调用者是受益人
    if (fundingGoalReached && beneficiary == msg.sender) {

        //将所有捐款从合约中给受益人
        beneficiary.transfer(amountRaised);

        FundTransfer(beneficiary, amount, false);
    }
}

  

4、如何使用众筹合约

  示例中所使用的众筹合约代码:

pragma solidity 0.4.16;
/**
 * 一个简单的代币合约。
 */
 contract token {

     string public standard = 'https://mshk.top';
     string public name; //代币名称
     string public symbol; //代币符号比如'$'
     uint8 public decimals = 2;  //代币单位,展示的小数点后面多少个0,和以太币一样后面是是18个0
     uint256 public totalSupply; //代币总量
     /* This creates an array with all balances */
     mapping (address => uint256) public balanceOf;

     event Transfer(address indexed from, address indexed to, uint256 value);  //转帐通知事件


     /* 初始化合约,并且把初始的所有代币都给这合约的创建者
      * @param _owned 合约的管理者
      * @param tokenName 代币名称
      * @param tokenSymbol 代币符号
      */
     function token(address _owned, string tokenName, string tokenSymbol) {
         //合约的管理者获得的代币总量
         balanceOf[_owned] = totalSupply;

         name = tokenName;
         symbol = tokenSymbol;

     }

     /**
      * 转帐,具体可以根据自己的需求来实现
      * @param  _to address 接受代币的地址
      * @param  _value uint256 接受代币的数量
      */
     function transfer(address _to, uint256 _value){
       //从发送者减掉发送额
       balanceOf[msg.sender] -= _value;

       //给接收者加上相同的量
       balanceOf[_to] += _value;

       //通知任何监听该交易的客户端
       Transfer(msg.sender, _to, _value);
     }

     /**
      * 增加代币,并将代币发送给捐赠新用户
      * @param  _to address 接受代币的地址
      * @param  _amount uint256 接受代币的数量
      */
     function issue(address _to, uint256 _amount) public{
         totalSupply = totalSupply + _amount;
         balanceOf[_to] += _amount;

         //通知任何监听该交易的客户端
         Transfer(this, _to, _amount);
     }
  }

/**
 * 众筹合约
 */
contract Crowdsale is token {
    address public beneficiary = msg.sender; //受益人地址,测试时为合约创建者
    uint public fundingGoal;  //众筹目标,单位是ether
    uint public amountRaised; //已筹集金额数量, 单位是wei
    uint public deadline; //截止时间
    uint public price;  //代币价格
    bool public fundingGoalReached = false;  //达成众筹目标
    bool public crowdsaleClosed = false; //众筹关闭


    mapping(address => uint256) public balance; //保存众筹地址

    //记录已接收的ether通知
    event GoalReached(address _beneficiary, uint _amountRaised);

    //转帐通知
    event FundTransfer(address _backer, uint _amount, bool _isContribution);

    /**
     * 初始化构造函数
     *
     * @param fundingGoalInEthers 众筹以太币总量
     * @param durationInMinutes 众筹截止,单位是分钟
     * @param tokenName 代币名称
     * @param tokenSymbol 代币符号
     */
    function Crowdsale(
        uint fundingGoalInEthers,
        uint durationInMinutes,
        string tokenName,
        string tokenSymbol
    ) token(this, tokenName, tokenSymbol){
        fundingGoal = fundingGoalInEthers * 1 ether;
        deadline = now + durationInMinutes * 1 minutes;
        price = 500 finney; //1个以太币可以买 2 个代币
    }


    /**
     * 默认函数
     *
     * 默认函数,可以向合约直接打款
     */
    function () payable {

        //判断是否关闭众筹
        require(!crowdsaleClosed);
        uint amount = msg.value;

        //捐款人的金额累加
        balance[msg.sender] += amount;

        //捐款总额累加
        amountRaised += amount;

        //转帐操作,转多少代币给捐款人
        issue(msg.sender, amount / price * 10 ** uint256(decimals));
        FundTransfer(msg.sender, amount, true);
    }

    /**
     * 判断是否已经过了众筹截止限期
     */
    modifier afterDeadline() { if (now >= deadline) _; }

    /**
     * 检测众筹目标是否已经达到
     */
    function checkGoalReached() afterDeadline {
        if (amountRaised >= fundingGoal){
            //达成众筹目标
            fundingGoalReached = true;
            GoalReached(beneficiary, amountRaised);
        }

        //关闭众筹
        crowdsaleClosed = true;
    }


    /**
     * 收回资金
     *
     * 检查是否达到了目标或时间限制,如果有,并且达到了资金目标,
     * 将全部金额发送给受益人。如果没有达到目标,每个贡献者都可以退出
     * 他们贡献的金额
     */
    function safeWithdrawal() afterDeadline {

        //如果没有达成众筹目标
        if (!fundingGoalReached) {
            //获取合约调用者已捐款余额
            uint amount = balance[msg.sender];

            if (amount > 0) {
                //返回合约发起者所有余额
                msg.sender.transfer(amount);
                FundTransfer(msg.sender, amount, false);
                balance[msg.sender] = 0;
            }
        }

        //如果达成众筹目标,并且合约调用者是受益人
        if (fundingGoalReached && beneficiary == msg.sender) {

            //将所有捐款从合约中给受益人
            beneficiary.transfer(amountRaised);

            FundTransfer(beneficiary, amount, false);
        }
    }
}

  使用之前的教程《Go-Ethereum 1.7.2 结合 Mist 0.9.2 实现代币智能合约的实例》,创建四个帐号,分别是为 主帐号张三李四王五,然后使用已经通过 挖矿 得到以太币的 主帐号 分别给 张三李四王五每人 100以太币.

mshk.top

  
  打开 Mist,连接上 geth 以后,点击 合约 -> 部署合约 ,选择 张三 来创建这个合约,将代码贴入,然后右侧选择 Crowdsale,Funding goal in ethers 众筹以太币的总数我们设置为 100Duration in minutes 众筹截止时间,我们设置为 20 分钟,Token name 代币的名称设置为 陌上花开,Token Symbol 代币符号设置为 $

mshk.top

  
  合约创建成功以后,点击右上方的 合约,然后点击 陌上花开 众筹合约,进行详情页,点击右侧的 存入以太币,我们分别使用 李四 购买 70 ether 的代币、王五 购买 50 ether 的代币.

mshk.top

  
  购买成功以后,可以看到,在 钱包 页面 李四王五 和帐户上都多了两个图标,说明他们购买的代币已经到帐,并且相关的余额也发生了变化。而 张三 的比特币是 99,98 ether,因为只要执行合约就需要花费一些 gas

mshk.top

  
  之前我们设置的代币小数后面 2个0,而1个以太币可以购买2个代币,看下图 李四王五 两个人购买的数量上是对的。

mshk.top

  
  在 众筹合约截止时间以后,我们调用 afterDeadline 方法,来判断众筹目标是否达成。如果严谨一些应该只允许管理员来操作这个方法,因为是测试,写的随意一些,任何人都可以调用。调用成功后,在众筹合约的详情页,Funding goal reachedCrowdsale closed 的值都为Yes

mshk.top

  
  如果众筹失败或者是在众筹过程中,捐赠人反悔了,想要收回捐赠的以太币,可以调用 safeWithdrawal 方法,取回之前捐赠的以太币。如果众筹成功后,是无法取回的。

  张三 的比特币是99,98 ether,在众筹结束后。调用 safeWithdrawal 方法,会将众筹合约中的所有比特币,转到 张三 的帐户地址下。
  
mshk.top

  
  一个简单的众筹合约做完了。通过这个例子,可以发散思维做其他很多智能合约,比如实名/匿名投票、淘宝交易合约等。

5、扩展阅读

ico众筹合约代码分析


博文作者:迦壹
博客地址:Go-Ethereum 1.7.2 结合 Mist 0.9.2 实现众筹合约的实例
转载声明:可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明,谢谢合作!


posted @ 2017-11-09 16:11  Lion  阅读(585)  评论(0编辑  收藏  举报