【以太坊开发】手把手教你发布众筹智能合约进行ICO

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第1张图片

前言

上一篇:【以太坊开发】手把手教你发个数字货币及怎样分配激励
token CBT我们已经发行好了,接下来该ICO了。

一、众筹流程及需求

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第2张图片

从上一篇文章我们了解到,token的发行总量为 200,000,000 个,我们本次只拿出3000个token来做众筹,在120分钟内众筹到3ETH,ETH与CBT汇率为 1:1000,即投资人投资1个ETH将拿到1000个CBT。
如果众筹成功,募集方得到ETH,投资人得到CBT;如果众筹失败,募集方退回ETH,投资人收到ETH。

二、编写智能合约

2.1 代币CBT智能合约

见上一篇文章里的智能合约代码,略

2.2 众筹智能合约
pragma solidity ^0.4.18;

library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

contract Ownable {
  address public owner;


  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public {
    owner = msg.sender;
  }


  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }


  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }

}

interface token {
    function transfer(address receiver, uint amount);
}

contract Crowdsale is Ownable {
    using SafeMath for uint256;
    address public beneficiary;
    uint public fundingGoal;
    uint public amountRaised;
    uint public deadline;
    uint public price;
    token public tokenReward;
    mapping(address => uint256) public balanceOf;
    bool public fundingGoalReached = false;
    bool public crowdsaleClosed = false;
    
    event GoalReached(address recipient, uint totalAmountRaised);
    event FundTransfer(address backer, uint amount, bool isContribution);
    
    
    address[] public funder;
    
    modifier afterDeadline() { if (now >= deadline) _; }
    
    function Crowdsale(
        address ifSuccessfulSendTo,
        uint fundingGoalInEthers,
        uint durationInMinutes,
        uint finneyCostOfEachToken,
        address addressOfTokenUsedAsReward) public {
            beneficiary = ifSuccessfulSendTo;
            fundingGoal = fundingGoalInEthers.mul(1 ether);
            deadline = now + durationInMinutes.mul(1 minutes);
            price = finneyCostOfEachToken.mul(1 finney);
            tokenReward = token(addressOfTokenUsedAsReward);
    }
    
    event LogPay(address sender, uint value, uint blance, uint amount, bool isClosed);
    function () public payable {
        require(!crowdsaleClosed);
        funder.push(msg.sender);
        balanceOf[msg.sender] = balanceOf[msg.sender].add(msg.value);
        amountRaised = amountRaised.add(msg.value);
        if(amountRaised >= fundingGoal) {
            crowdsaleClosed = true;
            fundingGoalReached = true;
        }
        emit LogPay(msg.sender, msg.value, balanceOf[msg.sender], amountRaised, crowdsaleClosed);
    }
    
    function getThisBalance() public constant returns (uint) {
        return this.balance;
    }
    
    function getNow() public constant returns (uint, uint) {
        return (now, deadline);
    }
    
    function setDeadline(uint minute) public onlyOwner {
        deadline = minute.mul(1 minutes).add(now);
    }
    
    function safeWithdrawal() public onlyOwner afterDeadline {
        if(amountRaised >= fundingGoal) {
            crowdsaleClosed = true;
            fundingGoalReached = true;
            emit GoalReached(beneficiary, amountRaised);
        } else {
            crowdsaleClosed = false;
            fundingGoalReached = false;
        }
        uint i;
        if(fundingGoalReached) {
            if(amountRaised > fundingGoal && funder.length>0) {
                address returnFunder = funder[funder.length.sub(1)];
                uint overFund = amountRaised.sub(fundingGoal);
                if(returnFunder.send(overFund)) {
                    balanceOf[returnFunder] = balanceOf[returnFunder].sub(overFund);
                    amountRaised = fundingGoal;
                }
            }
            for(i = 0; i < funder.length; i++) {
                tokenReward.transfer(funder[i], balanceOf[funder[i]].mul(1 ether).div(price));
                balanceOf[funder[i]] = 0;
            }
            if (beneficiary.send(amountRaised)) {
                emit FundTransfer(beneficiary, amountRaised, false);
            } else {
                fundingGoalReached = false;
            }
            
        } else {
            for(i = 0; i < funder.length; i++) {
                if (balanceOf[funder[i]] > 0 && funder[i].send(balanceOf[funder[i]])) {
                    amountRaised = 0;
                    balanceOf[funder[i]] = 0;
                    emit FundTransfer(funder[i], balanceOf[funder[i]], false);
                }
            }
        }
    }
    
}

三、准备工作

3.1 测试数据

管理员simon钱包地址(简称管理员):0x2D80ed00A372AF341887F473eD6241f1973C5D59
投资人ouyang钱包地址(简称ouyang):0x286870be6B845964b8EeeACA6f42942812269978
投资人yxm钱包地址(简称yxm):0x1251b21727401E8d386C5794a2d0375E400b2272
三个钱包地址的ETH余额情况:

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第3张图片

管理员钱包地址用来发行代币CBT合约、众筹合约。

3.2 调试工具

Remix + MyEtherWallet

四、测试用例

4.1 众筹成功

1、管理员创建代币合约,发行代币CBT;(本文将重新发布CBT,不同于上一篇文中提到的合约地址)
2、管理员simon创建众筹合约,开始众筹3个ETH;
3、给众筹合约地址分配3000个CBT,用于投资人回报;
4、投资人ouyang投资2个ETH;
5、投资人yxm投资5个ETH;
6、众筹截止时间到期,验收众筹结果,众筹成功后,募集方得到3个ETH,投资人ouyang得到2000个CBT,投资人yxm得到1000个CBT,由于募集额度已满,退款4个ETH。

4.2 众筹失败

1、按照3.1的第1、2、3、4依次执行;
2、众筹截止时间到期,验收众筹结果,众筹失败后,投资人yxm得到0个CBT,退款2个ETH。

五、部署与调试

5.1 众筹成功

1、在管理员钱包地址下创建CBT合约,初始化合约参数:
"20000000000000000000000","ColorBayToken","CBT"
得到CBT合约地址:0x73855095178ed6f343d3146e26be82c301d49320

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第4张图片
【以太坊开发】手把手教你发布众筹智能合约进行ICO_第5张图片

2、在管理员钱包地址下创建众筹合约,初始化合约参数:
"0x2D80ed00A372AF341887F473eD6241f1973C5D59",3,120,1,"0x73855095178ed6f343d3146e26be82c301d49320"
参数解释:CBT管理员钱包、总募资3个ETH、募资时间120分钟内、CBT价格、CBT合约地址,
得到众筹合约地址:0xd5576676c10efe8847ebf87accc4f40749c73edd

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第6张图片
【以太坊开发】手把手教你发布众筹智能合约进行ICO_第7张图片

3、给众筹合约钱包地址打3000个CBT
打开MyEtherWallet.com,点击"Send Ether & Tokens"链接,选择"MetaMask / Mist",填写众筹合约地址:0xd5576676c10efe8847ebf87accc4f40749c73edd,填写“Amount to Send”:3000,选择CBT类型,点击“Generate Transaction”生成交易,点击“Send Transaction”发送交易。

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第8张图片

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第9张图片

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第10张图片

4、投资人ouyang投资2个ETH
在MetaMask中切换到ouyang(...69978),打开MyEtherWallet.com,往众筹合约地址转账3个ETH。

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第11张图片

后面几步截图同上,略。

5、投资人yxm投资5个ETH
在MetaMask中切换到yxm(...b2272),,打开MyEtherWallet.com,往众筹合约地址转账5个ETH。
截图同上,略。

6、将募资时间设置成到期
为了调试方便,我们在众筹合约中加入了setTime函数,可自由调整募资时间。到目前为止我们的募资总额已经达到了7个ETH,募资已经达到目标3TH。
在MetaMask中切换到管理员(...C5D59),在Remix中手工调用众筹合约中的setTime函数让募资到期,传入参数0
验证:通过getNow函数对比现在时间戳和募资到期时间戳。

7、到期验收众筹情况
点击众筹合约中的safeWithdrawal函数来验收,
验证:查看管理员ETH余额,应该为:16.83...,即多出3个ETH。
在MetaMask中查看投资人ouyang(...69978)CBT余额应该为: 2000,000000000000000000
投资人yxm(...b2272)CBT余额应该为: 1000,000000000000000000
查看给yxm退款的4个ETH,余额应该为:9.977...
最终结果:

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第12张图片

5.2 众筹失败

1、沿用上2.1用例的第1、2、3、4、6步
2、到期验收众筹情况
在MetaMask中切换到管理员(...C5D59),点击众筹合约中的safeWithdrawal函数来验收,
验证:查看管理员ETH余额,应该为:13.830...,即没有增加ETH个数。
在MetaMask中查看投资人ouyang(...69978)CBT余额应该为: 0
查看给ouyang退款的2个ETH,余额应该为:12.917...
截图 略。

总结

众筹合约参考了《深入浅出区块链》如何通过以太坊智能合约来进行众筹(ICO),在此基础上做了一些改进:

  • 由原来的投资人一打ETH就将代币到账,改成只登记谁投了多少ETH,至于项目成功与否(是退款ETH还是打代币),改到募资时间截止后批量执行(截止时间可随时修改,可随时到期)
  • 增加一键操作:自动判断众筹是否成功,在众筹失败的情况下,自动批量退款ETH、清理币余额;
    在众筹成功的情况下,自动代币批量到账、ETH打给受益人(管理员)
  • 去除募资时间截止后投资人自行退款功能

遇到的坑:
搜索《深入浅出区块链》如何通过以太坊智能合约来进行众筹(ICO)一文中的合约代码tokenReward.transfer(msg.sender, amount / price);,应该改进成:tokenReward.transfer(msg.sender, amount * 1 ether / price);,因为这里传入的值,单位不会自动换算成eth,假设投资人投资3个eth,导致投资人得到的token个数是0.000000000000003,本来应该是3000个(本文合约的兑换汇率是1:1000)

本文原创,转载请注明出处:https://www.jianshu.com/p/3b94f6ebdf94
欢迎加微信与我一起学习交流!

【以太坊开发】手把手教你发布众筹智能合约进行ICO_第13张图片
微信交流

你可能感兴趣的:(【以太坊开发】手把手教你发布众筹智能合约进行ICO)