前
前面的文章实现了自己创建的 Token 在以太坊网络的发布, 这一篇, 接着来, 也是涉及到了更多的东西. 这次实现一个合约, 实现我们可以进行自动的 Token发放.
合约代码
此段代码选自官方的教程, 这里凭着个人的学习和理解, 加上注释
官方代码
pragma solidity ^0.4.16;interface token { function transfer(address receiver, uint amount); // 这里调用你的Token合约的函数接口}contract Crowdsale { address public beneficiary; // ICO的目标账户 uint public fundingGoal; // 筹集的目标金额 uint public amountRaised; // 当前募集的金额 uint public deadline; // 时间限制 uint public price; // 单token的定价 token public tokenReward; // 这里是token的地址 mapping(address => uint256) public balanceOf; // 地址和Token的映射关系 // 以上所有的数据都是Public的 bool fundingGoalReached = false; // 是否筹够 bool crowdsaleClosed = false; // 是否进行 event GoalReached(address recipient, uint totalAmountRaised); // 达标事件 event FundTransfer(address backer, uint amount, bool isContribution); // 转款事件 // 事件用于记录信息, Dapp读取事件 /** * Constrctor function * * Setup the owner */ 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); // 对上面的数据变量进行赋值 } /** * Fallback function * * The function without name is the default function that is called whenever anyone sends funds to a contract * 无名函数用于任何时候有人转钱了的回调, 这里是分配token的重点 */ function () payable { require(!crowdsaleClosed); // 保证Ico是没有结束 uint amount = msg.value; balanceOf[msg.sender] += amount; amountRaised += amount; tokenReward.transfer(msg.sender, amount / price); // 这里调用接口,把等价的Token分配给msg FundTransfer(msg.sender, amount, true); // 产生事件, 已经转Token了! } modifier afterDeadline() { if (now >= deadline) _; } // 修饰符, 是不是已经过了时间 /** * Check if goal was reached * * Checks if the goal or time limit has been reached and ends the campaign */ function checkGoalReached() afterDeadline { // 注意这里的修饰符, 如果已经超时了, 直接关闭ICO, if (amountRaised >= fundingGoal){ fundingGoalReached = true; // 判断是否筹齐, 齐了就发事件 GoalReached(beneficiary, amountRaised); } crowdsaleClosed = true; }
/**
* Withdraw the funds (退钱的) * * Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached, * sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw * the amount they contributed. * 检查时间, 和总额是否集齐, 如果达到 , 就把合约中的所有的钱款转入 受益人的账户. 如果没有达成,投钱的可以拿回自己的钱 */ function safeWithdrawal() afterDeadline { if (!fundingGoalReached) { // 没达成 uint amount = balanceOf[msg.sender]; // 投资人有多少Token balanceOf[msg.sender] = 0; // 把他的token 清零 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)) { // 这里和上面一样, 应该是Address的一个成员函数 FundTransfer(beneficiary, amountRaised, false); // 发送 转款事件 } else { //If we fail to send the funds to beneficiary, unlock funders balance fundingGoalReached = false; } } }}
其实可见, 一个实现token 众售的合约实际上还是比较容易理解的, 主要是 token 的发放, 退钱的, 和owner 用来提钱的这几个部分组成
Point
这里对上面的合约出现的新的Solidity要点进行说明
接口(interface)
接口这个东西 , 其实在上一篇文章中已经有说过 , 哪里没有用到. 是个很重要的东西 , 就是实现可以在合约中调用其他合约的 external 的函数.
interface token { function transfer(address receiver, uint amount); // 这里调用你的Token合约的函数接口}
这里定义 的一个接口, 主要是是实现调用 我们Token合约中的合约发放函数.
这里有个很棒的例子: 这个是著名的以太坊的加密猫, 这个项目当时可是导致了以太坊网络的严重堵塞. 这里选取里其中一段合约代码.
function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes) { Kitty storage kit = kitties[_id]; // if this variable is 0 then it's not gestating isGestating = (kit.siringWithId != 0); isReady = (kit.cooldownEndBlock <= block.number); cooldownIndex = uint256(kit.cooldownIndex); nextActionAt = uint256(kit.cooldownEndBlock); siringWithId = uint256(kit.siringWithId); birthTime = uint256(kit.birthTime); matronId = uint256(kit.matronId); sireId = uint256(kit.sireId); generation = uint256(kit.generation); genes = kit.genes;}
由名字和返回值可见 , 这个是blahblah 一大堆,用于获取某只Kitty 的所有信息的函数. 这里如果我们的合约突然想使用一下这里的喵的DNA怎么办? 很好, 我们可以使用接口了!
interface Kitty { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes );}
上面我们就定义了一个对应的接口, 如何使用呢?
address ckAddr = 0x06012c8cf97bead5deae237070f9587f8e7a266d;Kitty kittyInterface = Kitty(ckAddress); // 这里实例化这个接口!// 下面就是调用了uint dna;,,,,,,,,dna = kittyInterface.getKitty(0) // 假定是 0 号Kitty// 这里是多返回值
这样我们就可以获取dna了, 使用接口, 是不是很精妙?
回退函数
每一个合约有且仅有一个没有名字的函数。这个函数无参数,也无返回值。如果调用合约时,没有匹配上任何一个函数(或者没有传哪怕一点数据),就会调用默认的回退函数。
在示例代码中, 我们使用到了回退函数,(可见只有一个修饰符, 没有函数名的)
function () payable { require(!crowdsaleClosed); // 保证Ico是没有结束 uint amount = msg.value; balanceOf[msg.sender] += amount; amountRaised += amount; tokenReward.transfer(msg.sender, amount / price); // 这里调用接口,把等价的Token分配给msg FundTransfer(msg.sender, amount, true); // 产生事件, 已经转Token了!}
这个也是我们ICO合约的重要函数, 实现Token的分发. 根据定义, 我们如果直接对合约地址转账, 那么默认就会调用了回退函数, 这里很巧妙的利用来转账!
地址(Address)
上面的代码有一处是没看懂
if (msg.sender.send(amount)) { // 这里应该是有个发送请求(后面自己看看) FundTransfer(msg.sender, amount, false);} else { // 保持总额不变 balanceOf[msg.sender] = amount; }
这里感觉是一个请求, 会有返回值.于是下面就查证官方的Wiki看到
地址类型的成员
属性:balance
函数:send(),call(),delegatecall(),callcode()。
-
地址字面量(literal)
其实我们可以理解成一个常量, 在Solidity 里如果有字面量
0x06012c8cf97bead5deae237070f9587f8e7a266d
这个会直接被编译器理解为Address类型.
-
balance
这个如其定义一样
myAddress.balance
, 这个值就是我们的当前余额 -
send
这个比较重要, 之前没理解,怎么是下面这张形式
beneficiary.send(amountRaised)
最后Wiki’中知道了, 这个理解顺序是
this to beneficiary
, 就是由合约向其他的账户发送Ether.
后面的话
这次的crowdsale 结合上一次的token , 就可以发动一场轰轰烈烈的ICO了. 不过 希望能够只是以学习为目的, 尊重技术. 不要让这种没有意义的代码充斥 以太坊网络
Don’t be evil
后面还会有一篇,crowdsale在测试网络上的部署. 自己记录本身, 也是学习, 和大家共勉!