关于创建简单和高级ICO智能合约的最终指南

当涉及代币合同时,一切都很容易。 由于许多公司已经开发了可用于您的项目的受信任的ERC20解决方案,因此大部分工作已完成。

但是,ICO合同则完全不同。 您可以使用由多家不同公司提供的工具,但是它们很难实施,并且以特定方式设计,可能与您所需的设置不兼容。

因此,我的建议是始终从零创建自己的ICO智能合约。 很难,但是它将向您显示幕后情况以及其工作方式。 您还可以修改它们,并在以后为不同种类的ICO添加新功能。

在开始之前,我想警告您,由于Solidity会不断发生许多更改,因此该代码将来可能无法使用,因此请密切注意代码背后的思想,并尽可能地应用它们。

这是索引:

  1. 准备基本结构
  2. 制定众包合同
  3. 实施新的代币合约功能
  4. 高级ICO:可变令牌定价
  5. 结论

ICO智能合约的主要内容是buy()函数。 人们将使用此功能来发送以太币以换取令牌。 它必须完成以下任务:

  • 计算完成后,接收ETH并将其安全存储
  • 以适当的固定汇率将收到的ETH转换为代币
  • 将令牌发送给用户

请注意,分发令牌并使其锁定很重要,这样购买者才能在ICO完成之前不进行交换。 我们将在本指南结尾处实现此功能。

ICO合约还将具有一个后备应付功能,该功能会将调用重定向到buy()函数,作为直接购买代币的替代方法。

合同的基本结构如下:

 实用性0.4.24;合同众筹{函数()公共应付款项{buy();  } 
函数buy()公共应付款{}}

为了使事情井井有条,我喜欢添加一个功能,当ICO用修饰符完成时,将筹集的以太币分配给所有者。 仅出于安全目的:

 语用强度0.4.24; 
合同众筹{布尔ico已完成;
修饰符whenIcoCompleted { require(icoCompleted); _; }函数()公共应付款{buy(); }函数buy()公共应付款{}函数extractEther()公共whenIcoCompleted {}}

如您所见,我添加了一个名为whenIcoCompleted的修饰符,该修饰符仅在完成ICO后才允许执行函数extractEther()。 我们将在稍后看到何时可以考虑完成ICO。

任何好的ICO都需要一些基本变量,例如:

  • ICO开始时间,可以是时间戳,也可以是块号
  • ICO结束时间
  • 代币价格
  • 令牌地址
  • wei的融资目标是最小的以太坊单位
  • 定义ico是否已完成的布尔值
 语用强度0.4.24; 
合同众筹{ bool public icoCompleted; uint256 public icoStartTime; uint256公共icoEndTime; uint256 public tokenRate; 地址公共令牌地址; uint256公共资金目标; 修饰符whenIcoCompleted {require(icoCompleted); _; }函数()公共应付款{buy(); }函数buy()公共应付款{}函数extractEther()公共whenIcoCompleted {}}

现在是添加构造函数的好时机,因为在部署合同时我们将立即设置这些变量的初始值:

 构造函数(uint256 _icoStart,uint256 _icoEnd,uint256 _tokenRate,地址_tokenAddress,uint256 _fundingGoal)public {require(_icoStart!= 0 && _icoEnd!= 0 && _icoStart <_icoEnd && _tokenRate!= 0 && _toingGo! = 0);  icoStartTime = _icoStart;  icoEndTime = _icoEnd;  tokenRate = _tokenRate;  tokenAddress = _tokenAddress;  fundingGoal = _fundingGoal;} 

将构造函数写在修饰符下面很重要,因为它们就像您希望在代码中轻松找到的全局函数一样。

如您所见,我刚刚创建了基本的公共构造函数,用户可以在其中设置重要变量的值。 还有一个require()检查所有这些值是否已正确配置。

既然您已经有了主要变量,那么就可以开始实现购买和提取以太币的功能了。 因此,让我们这样做:

首先是extractEther函数。 我希望您自己实现它,然后再继续讨论在现实世界中如何实际执行它。 将此视为一个小游戏,答案就在下面。 可能有许多不同的有效答案。

这是我的方法:

 实用性0.4.24;合同众筹{bool public icoCompleted;  uint256 public icoStartTime;  uint256公共icoEndTime;  uint256 public tokenRate; 地址公共令牌地址;  uint256公共资金目标; 向公共所有者讲话; 修饰符whenIcoCompleted {require(icoCompleted);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }函数()公共应付款{buy();  }构造函数(uint256 _icoStart,uint256 _icoEnd,uint256 _tokenRate,地址_tokenAddress,uint256 _fundingGoal)public {require(_icoStart!= 0 && _icoEnd!= 0 && _icoStart <_icoEnd && _tokenRate!= 0 && _fund_ing! != 0);  icoStartTime = _icoStart;  icoEndTime = _icoEnd;  tokenRate = _tokenRate;  tokenAddress = _tokenAddress;  fundingGoal = _fundingGoal; 所有者= msg.sender;  }函数buy()公共应付款{}函数extractEther()公共whenIcoCompleted onlyOwner {owner.transfer(address(this).balance);  }} 

这是我所做的:

  • 我只是想提取存储在该Crowdsale合同中的以太币,所以我使用了全局转移功能将合同的余额转移给了它的所有者。
  • 因为我们还没有所有者,所以在顶部创建了该变量。 然后,在构造函数中将其值设置为msg.sender。
  • 最后,我创建了一个名为onlyOwner的修饰符,该修饰符将函数extractEther的执行仅限于所有者。 没必要,因为转账功能无论如何都会将资金发送给所有者,这是个好习惯,可以随时随地保留该选项。

之后,是时候实现主要功能了。 购买功能。 这是人们将以太币直接发送到购买代币的合同时将执行的功能。

此功能既庞大又复杂。 它处理真钱,因此正确计算代币很重要。 您可以尝试自己做。 我将逐步向您展示如何做到这一点。

您需要做的第一件事是根据发送的以太币数量和我们之前在构造函数中设置的tokenRate计算用户将收到的令牌数量。

创建一个名为tokensToBuy的变量:

  function buy()public payable {uint256 tokensToBuy;} 

该变量将包含用户应该接收的令牌数量。 我们如何知道要给他多少令牌?

好吧,我们可以访问msg.value全局变量,因为我们位于应付款函数中。 msg.value变量包含在wei中执行该功能时发送的以太币数量。

因此,如果一个人发送1个以太,则msg.value将是1000000000000000000或1等于18的幂,因为1个以太就是那么多wei。 Wei是最小的单位,它是智能合约内部所有计算中使用的单位。

我之前没有说过,但是让我们假设令牌“ Example”具有18个小数,就像以太一样。 这意味着1个令牌等于18 ETH的幂的1或:

  1个例子= ETH的1e18 Wei 

因此,在这种情况下,计算用户将获得多少令牌很简单。 令牌= msg.value,因为我们有相同的小数位数:

  function buy()public payable {uint256 tokensToBuy;  tokensToBuy = msg.value;} 

这很容易,不是吗? 如果我们的令牌有5个小数而不是18个小数怎么办? 然后,计算会更加复杂。 考虑一下:

  1个完整代币= 1e5个代币1个完整ETH = 1e18个ETH或Wei 

因此,要获得1个令牌,我们需要执行以下操作:

  1个完整令牌= 1个完整ETH * 1e5个令牌/ 1e18 Wei 

牢固地看起来像这样:

  function buy()public payable {uint256 tokensToBuy;  tokensToBuy = msg.value * 1e5 / 1以太币;} 

我们不是使用1个完整的ETH,而是使用用户发送的实际值,而是将其除以1个以太币,正好是合约内的1e18。

但是,问题在于我们说的是“每发送1个ETH,您只会收到1个令牌”,当然还有相应的小数转换,以便它们的值相同。

那不是大多数ICO的计划。 您想为每个ETH分配1000个令牌。 该数字恰好是我们的tokenRate变量。 因此,只需将其乘以:

  function buy()public payable {uint256 tokensToBuy;  tokensToBuy = msg.value * 1e5 / 1 ether * tokenRate;} 

现在,您将根据收到的以太币获得要购买的代币的确切数量。

如果要逆转此过程以根据令牌数量和速率获取ETH数量,只需用另一种方法进行相同的计算即可:

  1个完整代币= 1e5个代币1个完整ETH = 1e18 Wei1个完整ETH = 1个代币* 1e18 Wei / 1e5个代币 

添加令牌率将为:

  1个完整ETH = 1个令牌* 1e18 Wei / 1e5个令牌/ TokenRate 

坚定地将是:

 函数unBuy()public payable {uint256 tokensToConvert = 10;  //示例令牌uint256 weiFromTokens;  weiFromTokens = tokensToConvert * 1以太币/ tokenRate / 1e5;} 

我们稍后可能需要。 现在,如果您了解其工作原理,那就足够了。 这是我们的ICO合同现在的样子:

 实用性0.4.24;合同众筹{bool public icoCompleted;  uint256 public icoStartTime;  uint256公共icoEndTime;  uint256 public tokenRate; 地址公共令牌地址;  uint256公共资金目标; 向公共所有者讲话; 修饰符whenIcoCompleted {require(icoCompleted);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }函数()公共应付款{buy();  }构造函数(uint256 _icoStart,uint256 _icoEnd,uint256 _tokenRate,地址_tokenAddress,uint256 _fundingGoal)public {require(_icoStart!= 0 && _icoEnd!= 0 && _icoStart <_icoEnd && _tokenRate!= 0 && _fund_ing! != 0);  icoStartTime = _icoStart;  icoEndTime = _icoEnd;  tokenRate = _tokenRate;  tokenAddress = _tokenAddress;  fundingGoal = _fundingGoal; 所有者= msg.sender;  }函数buy()公用应付款{uint256 tokensToBuy;  tokensToBuy = msg.value * 1e5 / 1 ether * tokenRate;  }函数extractEther()public whenIcoCompleted onlyOwner {owner.transfer(address(this).balance);  }} 

您不必从代币合同中写出一个固定的数字(例如1e5),而是必须从令牌合约中获取实际价值,因为该价值会在ICO开发过程中发生变化。

我们还需要令牌合约的源代码,以便以后分发令牌。 因此,继续导入令牌合同,如下所示:

 实用可靠度0.4.24;库SafeMath {函数mul(uint256 a,uint256 b)内部纯收益(uint256 c){//气体优化:这比断言'a'不为零便宜,但//收益却丧失了如果也测试了“ b”。  //参见:https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if(a == 0){return 0;  } c = a * b;  assert(c / a == b); 返回c;  }函数div(uint256 a,uint256 b)内部纯返回(uint256){// assert(b> 0);  //除以0时,会自动抛出实心// // uint256 c = a / b;  // assert(a == b * c + a%b);  //在任何情况下都不能不返回return a / b;  }函数sub(uint256 a,uint256 b)内部纯返回值(uint256){assert(b  = a); 返回c;  }}合约代币{使用SafeMath for uint256; 事件传输(地址从以下地址开始索引,地址从以下位置开始索引,uint256值); 事件批准(地址索引所有者,地址索引支出者,uint256值); 映射(地址=> uint256)余额;  uint256 totalSupply_; 函数totalSupply()公共视图返回(uint256){return totalSupply_;  }函数传递(地址_to,uint256 _value)公共返回(布尔){require(_value 映射(地址=> uint256)); 函数transferFrom(address _from,address _to,uint256 _value)public return(bool){require(_value <= balances [_from]);  require(_value  = oldValue){allowed [msg.sender] [_ spender] = 0;  }其他{允许[msg.sender] [_ spender] = oldValue.sub(_subtractedValue);  }发出批准(msg.sender,_spender,允许[msg.sender] [_ spender]); 返回true;  }}合约ICOToken为Token {字符串public name ='ICOToken'; 字符串公共符号='ITK';  uint256公共小数= 18; 构造函数()public Token(){totalSupply_ = 100e24;  }}合同众筹{bool public icoCompleted;  uint256 public icoStartTime;  uint256公共icoEndTime;  uint256 public tokenRate; 地址公共令牌地址;  uint256公共资金目标; 向公共所有者讲话; 修饰符whenIcoCompleted {require(icoCompleted);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }函数()公共应付款{buy();  }构造函数(uint256 _icoStart,uint256 _icoEnd,uint256 _tokenRate,地址_tokenAddress,uint256 _fundingGoal)public {require(_icoStart!= 0 && _icoEnd!= 0 && _icoStart <_icoEnd && _tokenRate!= 0 && _fund_ing! != 0);  icoStartTime = _icoStart;  icoEndTime = _icoEnd;  tokenRate = _tokenRate;  tokenAddress = _tokenAddress;  fundingGoal = _fundingGoal; 所有者= msg.sender;  }函数buy()公用应付款{uint256 tokensToBuy;  tokensToBuy = msg.value * 1e5 / 1 ether * tokenRate;  }函数extractEther()public whenIcoCompleted onlyOwner {owner.transfer(address(this).balance);  }} 

这是我所做的:

  • 我导入了SafeMath.sol库,将其与令牌一起用于数学计算。
  • 然后,我添加了通过组合来自Open Zeppelin的基本,ERC20和标准令牌合约创建的Token.sol合约的代码。 他们提供了最好的令牌合同,因为它们经过了审核和安全。 被成千上万的项目使用。
  • 最后,我创建了合同ICOToken,在其中定义了基本信息,例如总供应量,名称,符号和小数。

现在,您可以在ICO合同中使用令牌合同。 这很重要,因为您想知道我们的代币有多少个小数,并且希望能够从同一合约中分配代币,因此您必须修改代币合约以尽可能最佳的方式分配代币。现在让我们继续购买功能。

添加令牌后,您想在Crowdsale合同中使用要使用的令牌地址创建该令牌的实例。 修改Crowdsale的构造函数和状态变量,如下所示:

 合同众筹{bool public icoCompleted;  uint256 public icoStartTime;  uint256公共icoEndTime;  uint256 public tokenRate;  ICOToken公共令牌;  uint256公共资金目标; 向公共所有者讲话; 修饰符whenIcoCompleted {require(icoCompleted);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }函数()公共应付款{buy();  }构造函数(uint256 _icoStart,uint256 _icoEnd,uint256 _tokenRate,地址_tokenAddress,uint256 _fundingGoal)public {require(_icoStart!= 0 && _icoEnd!= 0 && _icoStart <_icoEnd && _tokenRate!= 0 && _fund_ing! != 0);  icoStartTime = _icoStart;  icoEndTime = _icoEnd;  tokenRate = _tokenRate; 令牌= ICOToken(_tokenAddress);  fundingGoal = _fundingGoal; 所有者= msg.sender;  }函数buy()公用应付款{uint256 tokensToBuy;  tokensToBuy = msg.value * 1e5 / 1 ether * tokenRate;  }函数extractEther()public whenIcoCompleted onlyOwner {owner.transfer(address(this).balance);  }} 

我进行了以下更改:

  • 现在,我们将使用令牌实例,该实例的变量类型为“ ICOToken”,而不是使用tokenAddress。
  • 然后,我使用tokenAddress在构造函数中初始化该令牌实例。

现在,您可以与该令牌交互以获取buy函数中的小数位数:

  function buy()public payable {uint256 tokensToBuy;  tokensToBuy = msg.value *(10 ** token.decimals())/ 1个以太币* tokenRate;} 

这是相当复杂的,因此请确保通过阅读两次或三次直到它卡住来理解它。

您正在计算用户将获得多少令牌,具体取决于发送给购买或后备功能的ETH数量。 之后,您必须将这些令牌发送给购买者。 有不同的方法可以完成此操作。

我的首选解决方案是在令牌合约中创建一个函数,该函数允许Crowdsale将令牌发送给人们。 只有Crowdsale合同可以这样做。

首先,您必须通过添加以下两个功能来更改令牌合约:

 合约ICOToken为Token {字符串public name ='ICOToken'; 字符串公共符号='ITK';  uint256公共小数= 18; 地址公众众筹地址; 向公共所有者讲话; 仅修饰符人群{(require(msg.sender ==人群地址);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }构造函数()public Token(){totalSupply_ = 100e24; 所有者= msg.sender;  }函数setCrowdsale(address _crowdsaleAddress)public onlyOwner {require(_crowdsaleAddress!= address(0)); 群众销售地址= _群众销售地址;  }函数buyTokens(address _receiver,uint256 _amount)仅公开众筹{require(_receiver!= address(0));  require(_amount> 0); 转移(_receiver,_amount);  }} 

这是我所做的:

  • 我添加了owner和crowdsaleAddress变量。
  • 然后,我创建了2个修饰符,onlyOwner和onlyCrowdsale。
  • 我修改了构造函数以设置所有者的地址。
  • 我创建了setCrowdale()函数,该函数将用于设置rowdsaleAddress变量的值。
  • 最后,我创建了buyTokens()函数,该函数用于将令牌发送给参与ICO的人员。 此功能只能通过Crowdsale合同执行,这就是为什么我们需要它的地址。

请注意,在buyTokens()中使用的transfer()函数是ERC20标准中的函数,这意味着我们必须指定接收方和要发送的令牌数量。 不要将其与全局transfer()函数混淆,该函数具有1个参数,可以将以太币从该合约转移到另一个地址。

切记在ICO之前部署令牌并使用setCrowdsale()函数配置众筹地址,以免发生任何错误。 我喜欢在记事本中编写步骤,以记住部署ICO和令牌合约时如何进行,因为将需要进行这种配置。

步骤列表对于避免错误至关重要。 它看起来像这样:

 部署令牌合同使用刚刚部署的合同的令牌地址部署Crowdsale合同使用setCrowdsale()函数在令牌合同内设置Crowdsale地址以分发令牌将Crowdsale地址公开给您的投资者,以便他们可以向其发送以太币以进行购买代币 

所有这些还有一个重要的问题。 您必须确保在ICO结束之前将代币锁定,以防止人们在众筹运行时以较低的价格在交易所上交易代币。

这可能会使投资者感到恐惧,因为他们会看到代币以可能更低的价格出售到其他地方。

我们可以通过令牌合约中的几个函数来做到这一点。 首先创建一个名为afterCrowdsale的修饰符,如下所示:

 合约ICOToken为Token {字符串public name ='ICOToken'; 字符串公共符号='ITK';  uint256公共小数= 18; 地址公众众筹地址; 向公共所有者讲话;  uint256公共ICOEndTime; 仅修饰符人群{(require(msg.sender ==人群地址);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }修饰符afterCrowdsale {require(now> ICOEndTime || msg.sender == growsaleAddress);  _;  }构造函数(uint256 _ICOEndTime)public Token(){require(_ICOEndTime> 0);  totalSupply_ = 100e24; 所有者= msg.sender;  ICOEndTime = _ICOEndTime;  }函数setCrowdsale(address _crowdsaleAddress)public onlyOwner {require(_crowdsaleAddress!= address(0)); 群众销售地址= _群众销售地址;  }函数buyTokens(address _receiver,uint256 _amount)仅公开众筹{require(_receiver!= address(0));  require(_amount> 0); 转移(_receiver,_amount);  }} 

我创建了该修饰符,添加了一个名为ICOEndTime的变量,并在构造函数中设置了该变量的值,以便以后可以对其进行更改。 请注意,将允许众筹地址转移在ICO期间分发令牌所需的令牌。

使用修改后的构造函数,部署顺序将有所不同:

 使用刚刚结束的ICO部署令牌合同使用刚刚部署的合同的令牌地址部署Crowdsale合同使用setCrowdsale()函数设置令牌合同中的Crowdsale地址以分发令牌将Crowdsale地址公开给您的投资者,以便他们可以发送以太币购买代币 

之后,您可以覆盖转移和限额功能以锁定它们,直到ICO结束:

  /// @notice覆盖函数,直到ICO功能转移结束为止才允许令牌转移(地址_to,uint256 _value)public afterCrowdsale return(bool){return super.transfer(_to,_value);} /// @notice重写该功能以在ICO功能结束之前才允许令牌传输。transferFrom(address _from,address _to,uint256 _value)public afterCrowdsale return(bool){return super.transferFrom(_from,_to,_value);} /// @notice重写功能以禁止令牌传输,直到ICO功能批准为止(地址_spender,uint256 _value)公开之后afterCrowdsale return(bool){return super.approve(_spender,_value);} /// @notice覆盖功能允许令牌传输,直到ICO功能结束为止。addApproval(地址_spender,uint _addedValue)public afterCrowdsale返回(成功成功){return super.increaseApproval(_spender,_addedValue);} /// @notice重写该功能以禁止令牌传输,直到的结尾  ICO功能reduceApproval(地址_spender,uint _subtractedValue)在众筹后返回(布尔成功){返回super.decreaseApproval(_spender,_subtractedValue);}功能EmergencyExtract()仅外部所有者{owner.transfer(address(this).balance);} 

我还添加了EmergencyExtract()函数以提取存储在该合约中的以太币,以防有人向其发送以太币。 永远失去那个以太币将是非常糟糕的。

利用错误并保留金钱或与发送者联系以退还这些金额。

这是最终的令牌合同供您参考,请记住,您可以在文章结尾的github链接中看到更新的代码:

 合约ICOToken为Token {字符串public name ='ICOToken'; 字符串公共符号='ITK';  uint256公共小数= 18; 地址公众众筹地址; 向公共所有者讲话;  uint256公共ICOEndTime; 仅修饰符人群{(require(msg.sender ==人群地址);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }修饰符afterCrowdsale {require(now> ICOEndTime || msg.sender == growsaleAddress);  _;  }构造函数(uint256 _ICOEndTime)public Token(){require(_ICOEndTime> 0);  totalSupply_ = 100e24; 所有者= msg.sender;  ICOEndTime = _ICOEndTime;  }函数setCrowdsale(address _crowdsaleAddress)public onlyOwner {require(_crowdsaleAddress!= address(0)); 群众销售地址= _群众销售地址;  }函数buyTokens(address _receiver,uint256 _amount)仅公开众筹{require(_receiver!= address(0));  require(_amount> 0); 转移(_receiver,_amount);  } /// @notice重写函数,直到ICO函数transfer(address _to,uint256 _value)结束之前才允许令牌传输public afterCrowdsale return(bool){return super.transfer(_to,_value);  } /// @notice重写功能,直到ICO函数transferFrom(address _from,address _to,uint256 _value)结束之前才允许令牌传输。public afterCrowdsale return(bool){return super.transferFrom(_from,_to,_value) ;  } /// @notice覆盖函数,直到ICO函数结束为止才允许令牌传输批准(地址_spender,uint256 _value)public afterCrowdsale return(bool){return super.approve(_spender,_value);  } /// @notice覆盖该功能,直到ICO功能结束之前才允许令牌传输creaseApproval(address _spender,uint _addedValue)public afterCrowdsale return(bool success){return super.increaseApproval(_spender,_addedValue);  } /// @notice重写功能,直到ICO功能结束之前才允许令牌传输reduceApproval(address _spender,uint _subtractedValue)public afterCrowdsale return(bool success){return super.decreaseApproval(_spender,_subtractedValue);  }函数EmergencyExtract()外部onlyOwner {owner.transfer(address(this).balance);  }} 

如您所见,它非常复杂,因此在继续之前一定要了解它的每个部分。 现在,您可以像这样创建Crowdsale合同的整个buy()函数:

 合同众筹{bool public icoCompleted;  uint256 public icoStartTime;  uint256公共icoEndTime;  uint256 public tokenRate;  ICOToken公共令牌;  uint256公共资金目标; 向公共所有者讲话;  uint256个公共代币  uint256 public etherRaised; 修饰符whenIcoCompleted {require(icoCompleted);  _;  }修饰符onlyOwner {require(msg.sender == owner);  _;  }函数()公共应付款{buy();  }构造函数(uint256 _icoStart,uint256 _icoEnd,uint256 _tokenRate,地址_tokenAddress,uint256 _fundingGoal)public {require(_icoStart!= 0 && _icoEnd!= 0 && _icoStart <_icoEnd && _tokenRate!= 0 && _fund_ing! != 0);  icoStartTime = _icoStart;  icoEndTime = _icoEnd;  tokenRate = _tokenRate; 令牌= ICOToken(_tokenAddress);  fundingGoal = _fundingGoal; 所有者= msg.sender;  }函数buy()公共应付款{require(tokensRaised <fundingGoal);  require(现在 icoStartTime);  uint256 tokensToBuy;  uint256 etherUsed = msg.value;  tokensToBuy = etherUsed *(10 ** token.decimals())/ 1 ether * tokenRate;  //检查我们是否已达到并超出了筹资目标,以退还超出的代币和以太if(tokensRaised + tokensToBuy> fundingGoal){uint256 beyondTokens = tokensRaised + tokensToBuy -fundingGoal;  uint256 beyondEther;  //将超出的令牌转换为以太币,并退还以太超过的以太币=超出的令牌* 1以太币/ tokenRate / token.decimals();  msg.sender.transfer(exceedingEther);  //将要购买的令牌更改为新的数字tokensToBuy-=超出令牌;  //更新以太使用过的计数器etherUsed-= beyondEther;  } //将令牌发送给买方token.buyTokens(msg.sender,tokensToBuy);  //增加筹集的令牌和以太筹集的状态变量tokensRaised + = tokensToBuy;  etherRaised + = etherUsed;  }函数extractEther()public whenIcoCompleted onlyOwner {owner.transfer(address(this).balance);  }} 

这是我所做的:

  • 我添加了2个变量: tokenRaised和etherRaised,它们将用作计数器来跟踪分配的令牌数量。
  • 然后,我检查了ICO是否仍然开放:通过检查我们是否已达到筹资目标或ICO时间是否结束。
  • 接下来,我要检查该人想要购买的代币数量是否超过了筹资目标 。 在这种情况下,我要退还已发送的以太币并更新要购买的代币数量。
  • 最后,我使用buyTokens()函数:从令牌合约中转移这些令牌,并使用etherRaised更新tokenRaised以跟踪这些重要值。

就是这样 您现在知道了如何创建ICO合同。 人们将能够在ICO时间到来时购买代币,而在达到资金目标或ICO时间结束时ICO将停止。

您只需要确保遵循部署步骤列表,即可确保令牌和众包合同均具有100%的功能,然后分发众包地址,以便人们使用与ERC20兼容的任何钱包(如metamask)直接向其发送ETH。或myetherwallet。

在以下部分中,您将找到有关高级Crowdsale合同的其他信息,这些合同具有我过去使用的一些有趣功能。 请务必阅读并尝试在此处看到的所有内容,即使它只是将代码复制并粘贴到remix上也是如此。


开发基本的Crowdsale合同后,由于每个公司都有不同的目标集,因此通常必须为ICO合同编写其他功能。

有非常复杂和非常简单的众包合同。 事实是,您必须使用简单的Crowdsale合同的代码,并在此基础上开展工作,以使开发人员更轻松地完成所有工作。

接下来,我将向您展示如何向ICO添加可变令牌定价,以使其更加有趣。

在许多项目中,代币的价格会随着供应减少而增加,以激励早期贡献者,从而ICO开始正确的道路。

例如,以下是可变令牌定价用于ICO的工作方式:

  • 我有1亿枚代币
  • 我想以1 ETH = 5000个代币的价格出售前2500万个
  • 然后高达5000万的比率变得更加昂贵:1 ETH = 4000令牌
  • 高达7500万,即:1 ETH = 3000个代币
  • 多达1亿个以太坊= 2000个代币

但是,实现起来并不容易。 首先,您必须创建4个不同的状态速率变量,如下所示:

  uint256公共费率一个= 5000; uint256公共费率两个= 4000; uint256公共费率Three = 3000; uint256公共费率四= 2000; 

在继续之前,请在Crowdsale合同的顶部添加SafeMath库,以进行安全的数学计算,如下所示:

 合同众筹{ 
为uint256使用SafeMath; //在下面添加过去的代码
}

然后,您必须修改buy()函数以根据当时筹集的代币使用适当的汇率,请记住我们在Crowdsale合同内:

  uint256公共费率One = 5000; uint256公共费率Two = 4000; uint256公共费率Three = 3000; uint256公共费率4 = 2000; uint256公共limitTierOne = 25e6 *(10 ** token.decimals()); uint256公共limitTierTwo = 50e6 *(10 * * token.decimals()); uint256 public limitTierThree = 75e6 *(10 ** token.decimals()); uint256 public limitTierFour = 100e6 *(10 ** token.decimals()); 
function buy()public payable {require(tokensRaised <fundingGoal); require(现在 icoStartTime); uint256 tokensToBuy; uint256 etherUsed = msg.value;
//如果筹集的代币少于2500万(带小数),则应用第一率
if(tokensRaised <limitTierOne){
//第1层
tokensToBuy = etherUsed *(10 ** token.decimals())/ 1 ether * rateOne;

//如果您要购买的代币数量超出该等级
if(tokensRaised + tokensToBuy> limitTierOne){
tokensToBuy = calculateExcessTokens(etherUsed,limitTierOne,1,rateOne);
}
} else if(tokensRaised> = limitTierOne && tokensRaised <limitTierTwo){
// 2级
tokensToBuy = etherUsed *(10 ** token.decimals())/ 1 ether * rateTwo;

//如果您要购买的代币数量超出该等级
if(tokensRaised + tokensToBuy> limitTierTwo){
tokensToBuy = calculateExcessTokens(etherUsed,limitTierTwo,2,rateTwo);
}
} else if(tokensRaised> = limitTierTwo && tokensRaised <limitTierThree){
//第三层
tokensToBuy = etherUsed *(10 ** token.decimals())/ 1 ether * rateThree;

//如果您要购买的代币数量超出该等级
if(tokensRaised + tokensToBuy> limitTierThree){
tokensToBuy = calculateExcessTokens(etherUsed,limitTierThree,3,rateThree);
}
} else if(tokensRaised> = limitTierThree){
//第4层
tokensToBuy = etherUsed *(10 ** token.decimals())/ 1 ether * rateFour;
}
//检查我们是否已达到并超过了筹款目标,以退还超出数量的代币和以太币
if(tokensRaised + tokensToBuy> fundingGoal){
uint256超过令牌=令牌已筹集+令牌要购买-fundingGoal;
uint256 beyondEther;

//将超出的代币转换为以太币并退还以太币
beyondEther = beyondTokens * 1 ether / tokenRate / token.decimals();
msg.sender.transfer(exceedingEther);

//更改代币以购买新号码
tokensToBuy-=超出令牌数;

//更新以太币的计数器
etherUsed-=超出以太币;
}

//将代币发送给买方
token.buyTokens(msg.sender,tokensToBuy);

//增加令牌引发和以太币引发的状态变量
tokensRaised + = tokensToBuy;}

您认为您可以应付吗? 这是我所做的:

  • 我添加了4个limitTiertier状态变量,指示何时应应用费率。 如果要更改层数限制,以使价格上涨得更快或更慢,这将非常有用。
  • 然后,在buy()函数内部,我进行了计算以查看何时应应用每个层。

您可能已经注意到,如果要购买的代币数量超出每个等级,就会使用一个称为calculateExcessTokens的函数。 该函数如下所示:

 函数calculateExcessTokens
uint256金额,
uint256个令牌
uint256 tierSelected,
uint256 _rate
)公共回报(uint256 totalTokens){
require(数量> 0 && tokensThisTier> 0 && _rate> 0);
require(tierSelected> = 1 && tierSelected <= 4); uint256 weiThisTier = tokensThisTier.sub(tokensRaised).div(_rate);
uint256 weiNextTier = amount.sub(weiThisTier);
uint256 tokensNextTier = 0;
bool returnTokens = false; //如果最后一层的weis过多,请退还那些
if(tierSelected!= 4)
tokensNextTier = calculateTokensTier(weiNextTier, tierSelected.add(1));
其他
returnTokens = true; totalTokens = tokensThisTier.sub(tokensRaised).add(tokensNextTier); // Do the transfer at the end
if(returnTokens) msg.sender.transfer(weiNextTier);
}function calculateTokensTier (uint256 weiPaid, uint256 tierSelected)
internal constant returns(uint256 calculatedTokens)
{
require(weiPaid > 0);
require(tierSelected >= 1 && tierSelected <= 4);if(tierSelected == 1)
calculatedTokens = weiPaid * (10 ** token.decimals()) / 1 ether * rateOne;
else if(tierSelected == 2)
calculatedTokens = weiPaid * (10 ** token.decimals()) / 1 ether * rateTwo;
else if(tierSelected == 3)
calculatedTokens = weiPaid * (10 ** token.decimals()) / 1 ether * rateThree;
其他
calculatedTokens = weiPaid * (10 ** token.decimals()) / 1 ether * rateFour;
}

Those 2 functions are used to calculate how many tokens are exceeding each tier. Imagine this scenario:

  • There are 100 million tokens sold on the ICO with 4 tiers separated by 25 million. So the first tier is applied to up to 25 million tokens, the second to 50 million and so on.
  • What if there are 24 million tokens sold already and you want to buy 2 million tokens?
  • Because we decided that up to 25 million those tokens will have the price of the first tier (cheaper tokens), the right way to solve this problem is to sell 1 million tokens for the price of rateOne and the remaining 1 million tokens for the price of rateTwo .
  • That’s why I’m using a function called calculateExcessTokens which returns you how many tokens are exceeding the selected tier. In the past case it would return 1 million tokens which are exceeding the first tier.
  • Then I use the calculateTokensTier function to calculate how much those 1 million remaining tokens will cost in ETH.

In summary, the problem of exceeding tokens is solved with the function calculateExcessTokens along with calculateTokensTier to buy the tokens at the right price for everybody.

Note that this is not a perfect solution and it only works if the investor is able to buy less than 25 million tokens. If someone would buy 26 million or more, he could avoid the tiers and get the tokens for cheaper.

All said, this is one of the solutions that worked nicely for me in the past. You can use this variable token pricing system for making your own ICOs. You have the complete code in my github here from a real-world ICO I made.

You can also see the completed code here for your reference: https://github.com/merlox/sample-ico

恭喜你! You’ve learned how to create ICOs and how to raise money doing so. That’s the easy part. Now the hard thing to do is implement all this knowledge and actually do it in the real world where money is real.

If you make a mistake there, you can quite literally destroy lives. Be careful with this power. Pay attention to your code and re-read it 3 times. Have it audited by a third-party person until you know it has everything you need and want.

Remember to use the deployment list to make sure you are setting everything up when deploying the final ICO contracts and that they are working when needed. You don’t want them to be faulty when the ICO starts.

Join my exclusive email list to receive premium inside-only guides, tutorials and stories here: http://eepurl.com/dDQ2yX. I won’t spam you. In fact, I’ll send you less than 1 email per month.

You can get my ebook “Ethereum Developer: Learn Solidity From Scratch” here for 20 dollars: https://merunas.io/

You can get my complete “Ethereum Developer Video Course” here for 300 dollars: https://merunas.io/videocourse more details on the website.

Let me know if this tutorial helped you become a better Ethereum Developer to be able to create your own ICOs and maybe raise millions of dollars with the next big revolution. Stay tuned, clap the article and follow my blog here: https://medium.com/@merunasgrincalaitis