ICO众筹合约代码分析


  • qtum team

    ICO众筹合约代码分析

    ICO是以初始产生的数字加密货币作为投资回报的一种筹措资金的方式,它的概念源自证券界的Initial Public Offering(IPO,首次公开发行)。相较于传统意义上的IPO,ICO具有可以缩短投融资链、降低投融资门槛、流动性佳、全球性投资等优势。它可以让项目在最开始阶段就面对投资者,极大降低投融资双方的投资门槛,基于数字货币特性,ICO可以汇集全球投资者的资金。
    本文将对过往知名的ICO项目合约代码进行分析,方便大家参考学习。

    Augur

    Augur 是建立在以太坊平台上的去中心化预测市场平台,也是以太坊平台上第一个众筹的应用,利用 Augur任何人都可以为任何自己感兴趣的主题创建一个预测市场,并提供初始流动性。Augur 项目于2015年8月开启代币众筹,一共众筹到500万美元,代币名称为REP。

    Augur众筹既支持比特币也支持以太,众筹合约和Token交易合约是分为两个部分,众筹完成后再根据众筹时的以太坊地址分配Token。众筹合约源代码地址:https://github.com/AugurProject/token-sale
    Token交易源代码:https://github.com/AugurProject/augur-core/blob/develop/src/repContract.se

    Augur众筹时的solidity语法和现在相比有了很多变化。

    // Funders保存了众筹时的每一笔参与情况,amount记录以太数量、blockNum记录区块高度、address记录发送者地址。
    data Funders[](address, amount, blockNum)
    
    // Funders数组长度
    data funderNum
    
    // addrToFunderNum记录了每个账号的记录ID
    data addrToFunderNum[]
    
    // augur团队的以太坊账号地址
    data augurAddress
    
    // 众筹截止时间
    data deadline
    
    def init():
    	self.augurAddress = 0xa04fc9bd2be8bcc6875d9ebb964e8f858bcc1b4f
    	self.deadline = 432015
    
    // 参与众筹调用的函数
    def buyRep():
    	if(msg.value==0):
    		return(0)
    	if(block.number < self.deadline):
    	   // 将以太发送到augur团队的以太坊账号上
    		send(self.augurAddress, msg.value)
    		
    		// 记录每一笔众筹转账记录
    		self.Funders[self.funderNum].amount = msg.value
    		self.Funders[self.funderNum].blockNum = block.number
    		self.Funders[self.funderNum].address = tx.origin
    		
    		// 记录该用户的记录ID
    		self.addrToFunderNum[tx.origin] = self.funderNum
    		self.funderNum += 1
    		return(1)
    	else:
    		return(0)
     
     // 还有其他函数不展示了。
    

    众筹完成后,augur会统计众筹情况并将Token在另一个合约中进行分配。
    每个用户的代币数量保存在reputation对象中。

        data reputation[2**160]
    

    在init函数中定义代币的名称、符号、小数位、创建者和创建时间。

    def init():
        self.controller = 0x0
        self.name = "Reputation"
        self.symbol = "REP"
        self.decimals = 18
        self.creator = msg.sender
        self.creation = block.timestamp
    

    在setSaleDistribution函数中给众筹参与者分配代币REP,地址和代币数量通过数组参数的形式传递进来。

    def setSaleDistribution(addresses: address[], balances: uint256[]):
        i = 0
        numberOfAddresses = len(addresses)
        if(numberOfAddresses != len(balances)):
            ~invalid()
        if(self.seeded or !stillCreating()):
            ~invalid()
        // 确保只有创建者才可以进行代币分配
        if(msg.sender != self.creator):
            ~invalid()
        
        // 遍历数组,在reputation中设置各个用户的代币数,同时更新代币总数totalSupply
        while(i < numberOfAddresses):
            if(!self.reputation[addresses[i]] and !self.seeded):
                self.reputation[addresses[i]] = balances[i]
                self.totalSupply += balances[i]
                log(type = Transfer, 0, addresses[i], balances[i])
            i += 1
        if(self.totalSupply == 11000000 * WEI_TO_ETH):
            self.seeded = 1
        if(self.totalSupply > 11000000 * WEI_TO_ETH):
            ~invalid()
        return(1: uint256)
    

    用户可以通过transfer函数发送REP,通过totalSupply函数获取REP总发行数,通过balanceOf函数获取某个用户的REP数量,通过approve函数进行委托,通过transferFrom函数执行委托转账,已经和ERC20标准很接近了。

    FirstBlood

    第一滴血是一个可以让玩家随时随地参与竞赛并且获得赏金的去中心化电竞平台。第一滴血ICO于2016年9月26日开始,达成众筹额度仅耗费两分钟,共筹得约465,313ETH,发放代币1SF约7910万枚,每枚代币成本约为0.5元人民币。
    ICO合约源码地址:https://github.com/Firstbloodio/token

    兑换Token的代码如下,第一滴血使用了安全的数学运算函数,保证不会出现溢出等漏洞。

        function buyRecipient(address recipient, uint8 v, bytes32 r, bytes32 s) {
            
            // 可以自己买 也可以替别人购买
            bytes32 hash = sha256(msg.sender);
            if (ecrecover(hash,v,r,s) != signer) throw;
            
            // 判断是否在设定的众筹区块高度以内
            if (block.number<startBlock || block.number>endBlock || safeAdd(presaleEtherRaised,msg.value)>etherCap || halted) throw;
            
            // 根据价格计算Token数量
            uint tokens = safeMul(msg.value, price());
            
            // 更新balances中的记录
            balances[recipient] = safeAdd(balances[recipient], tokens);
            // 更新代币总数
            totalSupply = safeAdd(totalSupply, tokens);
            
            // 更新筹集的总以太数
            presaleEtherRaised = safeAdd(presaleEtherRaised, msg.value);
    
            // 将收到的以太立即发送到创建者账号上。
            if (!founder.call.value(msg.value)()) throw;
            // 打log
            Buy(recipient, msg.value, tokens);
        }
    

    当有紧急情况发生时,创建者可以调用halt函数,停止所有关于资金的操作,unhalt函数用户接触这种状态。

        function halt() {
            if (msg.sender!=founder) throw;
            halted = true;
        }
    
        function unhalt() {
            if (msg.sender!=founder) throw;
            halted = false;
        }
    

    allocateFounderTokens函数用于众筹完成后给创始人分配Token,
    allocateBountyAndEcosystemTokens函数用户生产系统中的奖励。
    这些生产的Token实际上都是放在创始人的账户上。

        function allocateFounderTokens() {
            if (msg.sender!=founder) throw;
            if (block.number <= endBlock + founderLockup) throw;
            if (founderAllocated) throw;
            if (!bountyAllocated || !ecosystemAllocated) throw;
            balances[founder] = safeAdd(balances[founder], presaleTokenSupply * founderAllocation / (1 ether));
            totalSupply = safeAdd(totalSupply, presaleTokenSupply * founderAllocation / (1 ether));
            founderAllocated = true;
            AllocateFounderTokens(msg.sender);
        }
    
        function allocateBountyAndEcosystemTokens() {
            if (msg.sender!=founder) throw;
            if (block.number <= endBlock) throw;
            if (bountyAllocated || ecosystemAllocated) throw;
            presaleTokenSupply = totalSupply;
            balances[founder] = safeAdd(balances[founder], presaleTokenSupply * ecosystemAllocation / (1 ether));
            totalSupply = safeAdd(totalSupply, presaleTokenSupply * ecosystemAllocation / (1 ether));
            balances[founder] = safeAdd(balances[founder], bountyAllocation);
            totalSupply = safeAdd(totalSupply, bountyAllocation);
            bountyAllocated = true;
            ecosystemAllocated = true;
            AllocateBountyAndEcosystemTokens(msg.sender);
        }
    

    Token交易部分,一血遵循了ERC20标准。

    Golem

    Golem 是一个去中心化的全球算力市场。Golem于2016年11月11日开启众筹,约半个小时完成众筹上限82万ETH,以当日ETH71元人民币的收盘价计算,共筹得约5822万元人民币,每枚代币成本约为0.071元人民币,代币名称为GNT。
    ICO合约源码地址: https://github.com/golemfactory/golem-crowdfunding

    Golem ICO众筹合约代码由Token.sol和GNTAllocation.sol两个文件组成,其中GNTAllocation.sol中定义了分配给Golem公司和开发者的Token数量,主要部分的合约写在Token.sol中。

    代码首先定义了Token的名称、符号、小数位、兑换比率、最大和最小众筹数。

        string public constant name = "Golem Network Token";
        string public constant symbol = "GNT";
        uint8 public constant decimals = 18;  // 18 decimal places, the same as ETH.
        uint256 public constant tokenCreationRate = 1000;
        uint256 public constant tokenCreationCap = 820000 ether * tokenCreationRate;
        uint256 public constant tokenCreationMin = 150000 ether * tokenCreationRate;
    

    然后用两个变量保存众筹开始和结束的区块高度, 会在合约创建时以参数的形式传递进来。

        uint256 public fundingStartBlock;
        uint256 public fundingEndBlock;
    

    totalTokens变量保存总Token发行数,balances字典保存每个账号拥有的Token数量

        uint256 totalTokens;
        mapping (address => uint256) balances;
    

    funding变量初始值设置为真,表明处在发行阶段。

        bool public funding = true;
    

    当众筹开始,用户可以向合约发送以太换取GNT。

        function create() payable external {
            // 判断是否处在发行阶段、是否在预设区块高度之内。
            if (!funding) throw;
            if (block.number < fundingStartBlock) throw;
            if (block.number > fundingEndBlock) throw;
    
            // 是否发送以太
            if (msg.value == 0) throw;
            // 发送的以太是否超出众筹额度
            if (msg.value > (tokenCreationCap - totalTokens) / tokenCreationRate)
                throw;
            
            // 根据比率计算兑换的Token数量
            var numTokens = msg.value * tokenCreationRate;
            // 更新totalTokens数值
            totalTokens += numTokens;
    
            // 更新balances中该用户的Token数量
            balances[msg.sender] += numTokens;
    
            // 打 Log 
            Transfer(0, msg.sender, numTokens);
        }
    

    众筹成功结束后,Golem 公司和开发者将会获得18%的GNT,同时合约上的以太被发送到Golem 公司的ETH账户上。
    Golem 公司和开发者的GNT会先保存在GNTAllocation合约中,6个月的锁定期之后才能取回。

        function finalize() external {
            // 判断众筹是否成功
            if (!funding) throw;
            if ((block.number <= fundingEndBlock ||
                 totalTokens < tokenCreationMin) &&
                totalTokens < tokenCreationCap) throw;
    
            // 将发行状态设置为假
            funding = false;
    
            // 计算Golem 公司和开发者应得到的GNT数量,并分配。
            uint256 percentOfTotal = 18;
            uint256 additionalTokens =
                totalTokens * percentOfTotal / (100 - percentOfTotal);
            totalTokens += additionalTokens;
            balances[lockedAllocation] += additionalTokens;
            Transfer(0, lockedAllocation, additionalTokens);
    
            // 将众筹所得的以太发送到golem公司账号
            if (!golemFactory.send(this.balance)) throw;
        }
    

    若众筹失败,用户可以取回以太

        function refund() external {
            // 判断众筹是否失败
            if (!funding) throw;
            if (block.number <= fundingEndBlock) throw;
            if (totalTokens >= tokenCreationMin) throw;
    
            // 减去对应的GNT数量
            var gntValue = balances[msg.sender];
            if (gntValue == 0) throw;
            balances[msg.sender] = 0;
            totalTokens -= gntValue;
            
            // 计算应返还的以太数量
            var ethValue = gntValue / tokenCreationRate;
            Refund(msg.sender, ethValue);
            // 返回以太
            if (!msg.sender.send(ethValue)) throw;
        }
    

    众筹成功后,用户可以通过transfer函数发送Token,通过totalSupply函数获取总GNT发行数,通过balanceOf函数获取某个用户的GNT数量,没有完全遵循ERC20标准。

    Bancor

    Bancor协议是一种基于以太坊的底层货币协议,通过区块链技术和智能合约实现小型加密数字货币之间的连续流动性和异步价格发现。Bancor于2017年6月13日开启众筹,数据显示,在ICO于18:00结束时已经筹集了超过39万个以太,发行了将近8000万的BNT。
    合约源码地址:https://github.com/bancorprotocol/contracts

    Bancor将众筹和Token交易部分的合约进行了剥离,CrowdsaleController对象在初始化时保持对token对象的引用,通过issue函数进行交互,同时大量使用了装饰器,使得代码可读性更高。
    兑换Token部分代码如下:

    // CrowdsaleController.sol
    
        function contributeETH()
            public
            payable
            between(startTime, endTime) // 通过装饰器可以让代码更优雅,逻辑更清晰
            returns (uint256 amount)
        {
            return processContribution();
        }
        
        function processContribution() private
            active
            etherCapNotReached(msg.value)
            validGasPrice
            returns (uint256 amount)
        {
            //计算Token数量
            uint256 tokenAmount = computeReturn(msg.value);
            // 以太转移到指定的受益账号中
            assert(beneficiary.send(msg.value)); 
            // 更新token众筹总数
            totalEtherContributed = safeAdd(totalEtherContributed, msg.value); 
            // 通过调用token对象的issue函数,更新该用户的Token数量
            token.issue(msg.sender, tokenAmount); // issue new funds to the contributor in the smart token
            // 通过调用token对象的issue函数,更新受益账号的Token数量
            token.issue(beneficiary, tokenAmount); 
            // 打log
            Contribution(msg.sender, msg.value, tokenAmount);
            return tokenAmount;
        }
    

    token是一个SmartToken合约对象,继承自Owned合约。

    // Owned.sol
    contract Owned is IOwned {
        address public owner;
        address public newOwner;
        
        // token对象创建时的所有者是合约的部署者。
        function Owned() {
            owner = msg.sender;
        }
        
        // 通过transferOwnership函数实现隶属关系的变化。
        function transferOwnership(address _newOwner) public ownerOnly {
            require(_newOwner != owner);
            newOwner = _newOwner;
        }
    }
    

    在部署合约的JavaScript代码中完成transferOwnership函数的调用。

    // CrowdsaleController.js
    async function initController(accounts, activate, startTimeOverride = startTimeInProgress) {
        token = await SmartToken.new('Token1', 'TKN1', 2);
        tokenAddress = token.address;
    
        let controller = await TestCrowdsaleController.new(tokenAddress, startTime, beneficiaryAddress, btcsAddress, realEtherCapHash, startTimeOverride);
        let controllerAddress = controller.address;
    
        if (activate) {
            await token.transferOwnership(controllerAddress);
            await controller.acceptTokenOwnership();
        }
    
        return controller;
    }
    

    这样,CrowdsaleController合约就拥有了对SmartToken合约关键函数调用的权利。SmartToken合约完全基于ERC20标准。

    QTUM

    Qtum AAL 量子链账户抽象层,打通了比特币和以太坊生态;Qtum MPOS 量子链互惠权益证明机制,打造了可拓展性的共识机制;Qtum DGP 量子链分布式治理协议,让区块链协议无缝升级。

    QTUM量子链将于2017年6月28日发布测试网络,正式版也将在下半年尽快上线,届时用户可以在使用内置的合约模版和Dapp商店一键在移动端发起自己的ICO和发行代币。

    参考资料


Log in to reply
 

Looks like your connection to QTUM was lost, please wait while we try to reconnect.