前言
上一篇:【以太坊开发】手把手教你发个数字货币及怎样分配激励
token CBT我们已经发行好了,接下来该ICO了。
一、众筹流程及需求
从上一篇文章我们了解到,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余额情况:
管理员钱包地址用来发行代币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
2、在管理员钱包地址下创建众筹合约,初始化合约参数:
"0x2D80ed00A372AF341887F473eD6241f1973C5D59",3,120,1,"0x73855095178ed6f343d3146e26be82c301d49320"
,
参数解释:CBT管理员钱包、总募资3个ETH、募资时间120分钟内、CBT价格、CBT合约地址,
得到众筹合约地址:0xd5576676c10efe8847ebf87accc4f40749c73edd
3、给众筹合约钱包地址打3000个CBT
打开MyEtherWallet.com,点击"Send Ether & Tokens"链接,选择"MetaMask / Mist",填写众筹合约地址:0xd5576676c10efe8847ebf87accc4f40749c73edd
,填写“Amount to Send”:3000,选择CBT类型,点击“Generate Transaction”生成交易,点击“Send Transaction”发送交易。
4、投资人ouyang投资2个ETH
在MetaMask中切换到ouyang(...69978
),打开MyEtherWallet.com,往众筹合约地址转账3个ETH。
后面几步截图同上,略。
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...
最终结果:
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
欢迎加微信与我一起学习交流!