В прошлом уроке мы добавили к нашему ICO выпуск токенов для основателей и на баунти программы. Для полноценного ICO нам осталось только добавить бонусы для инвесторов
Что такое бонусы для инвесторов и зачем это нужно
Бонусы — это вознаграждение в виде дополнительных токенов для инвесторов которые раньше всего купили наши токены. Такой подход сильно мотивирует инвесторов вложится пораньше и побольше. К примеру, если инвестор вложится в наш проект в первые два дня начала ICO то мы можем ему выпустить на 25% больше токенов к тем что он купил.
Вот так выглядела программа бонусов для Polybius ICO.
Стоимость одного PLBT (название токена Polybius) была 10$. Таким образом если бы вы купили один токен в первый день, то получили бы еще 2.5 бесплатно.
А вот так выглядела программа бонуcов в TenX ICO.
Добавляем бонусы в ICO
Пусть наша программа бонусов делится на четыре периода ICO .
- первая четверть +25%
- вторая четверть +10%
- третья четверть +5%
- четвертая без бонусов
Тогда если tokens — количество купленных инвесторами токенов, start — дата начала ICO, а period — продолжительность ICO в днях, то наш код расчета бонуcных токенов будет выглядеть так:
1 2 3 4 5 6 7 8 |
uint bonusTokens = 0; if(now < start + (period * 1 days).div(4)) { bonusTokens = tokens.div(4); } else if(now >= start + (period * 1 days).div(4) && now < start + (period * 1 days).div(4).mul(2)) { bonusTokens = tokens.div(10); } else if(now >= start + (period * 1 days).div(4).mul(2) && now start + < (period * 1 days).div(4).mul(3)) { bonusTokens = tokens.div(20); } |
Осталось только поправить функцию продажи токенов createTokens так, чтобы бонусные токены добавлялись к купленным:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function createTokens() isUnderHardCap saleIsOn payable { multisig.transfer(msg.value); uint tokens = rate.mul(msg.value).div(1 ether); uint bonusTokens = 0; if(now < start + (period * 1 days).div(4)) { bonusTokens = tokens.div(4); } else if(now >= start + (period * 1 days).div(4) && now < start + (period * 1 days).div(4).mul(2)) { bonusTokens = tokens.div(10); } else if(now >= start + (period * 1 days).div(4).mul(2) && now < start + (period * 1 days).div(4).mul(3)) { bonusTokens = tokens.div(20); } tokens += bonusTokens; token.mint(msg.sender, tokens); } |
Отлично, теперь полностью код нашего ICO выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
pragma solidity ^0.4.15; /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/179 */ contract ERC20Basic { uint256 public totalSupply; function balanceOf(address who) constant returns (uint256); function transfer(address to, uint256 value) returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address owner, address spender) constant returns (uint256); function transferFrom(address from, address to, uint256 value) returns (bool); function approve(address spender, uint256 value) returns (bool); event Approval(address indexed owner, address indexed spender, uint256 value); } /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { function mul(uint256 a, uint256 b) internal constant returns (uint256) { uint256 c = a * b; assert(a == 0 || c / a == b); return c; } function div(uint256 a, uint256 b) internal constant 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 constant returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal constant returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } /** * @title Basic token * @dev Basic version of StandardToken, with no allowances. */ contract BasicToken is ERC20Basic { using SafeMath for uint256; mapping(address => uint256) balances; /** * @dev transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint256 _value) returns (bool) { balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); Transfer(msg.sender, _to, _value); return true; } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address _owner) constant returns (uint256 balance) { return balances[_owner]; } } /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * @dev https://github.com/ethereum/EIPs/issues/20 * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract StandardToken is ERC20, BasicToken { mapping (address => mapping (address => uint256)) allowed; /** * @dev Transfer tokens from one address to another * @param _from address The address which you want to send tokens from * @param _to address The address which you want to transfer to * @param _value uint256 the amout of tokens to be transfered */ function transferFrom(address _from, address _to, uint256 _value) returns (bool) { var _allowance = allowed[_from][msg.sender]; // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met // require (_value <= _allowance); balances[_to] = balances[_to].add(_value); balances[_from] = balances[_from].sub(_value); allowed[_from][msg.sender] = _allowance.sub(_value); Transfer(_from, _to, _value); return true; } /** * @dev Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender. * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint256 _value) returns (bool) { // To change the approve amount you first have to reduce the addresses` // allowance to zero by calling `approve(_spender, 0)` if it is not // already 0 to mitigate the race condition described here: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 require((_value == 0) || (allowed[msg.sender][_spender] == 0)); allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } /** * @dev Function to check the amount of tokens that an owner allowed to a spender. * @param _owner address The address which owns the funds. * @param _spender address The address which will spend the funds. * @return A uint256 specifing the amount of tokens still available for the spender. */ function allowance(address _owner, address _spender) constant returns (uint256 remaining) { return allowed[_owner][_spender]; } } /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() { 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) onlyOwner { require(newOwner != address(0)); owner = newOwner; } } /** * @title Mintable token * @dev Simple ERC20 Token example, with mintable token creation * @dev Issue: * https://github.com/OpenZeppelin/zeppelin-solidity/issues/120 * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol */ contract MintableToken is StandardToken, Ownable { event Mint(address indexed to, uint256 amount); event MintFinished(); bool public mintingFinished = false; modifier canMint() { require(!mintingFinished); _; } /** * @dev Function to mint tokens * @param _to The address that will recieve the minted tokens. * @param _amount The amount of tokens to mint. * @return A boolean that indicates if the operation was successful. */ function mint(address _to, uint256 _amount) onlyOwner canMint returns (bool) { totalSupply = totalSupply.add(_amount); balances[_to] = balances[_to].add(_amount); Mint(_to, _amount); return true; } /** * @dev Function to stop minting new tokens. * @return True if the operation was successful. */ function finishMinting() onlyOwner returns (bool) { mintingFinished = true; MintFinished(); return true; } } contract SimpleTokenCoin is MintableToken { string public constant name = "Simple Coint Token"; string public constant symbol = "SCT"; uint32 public constant decimals = 18; } contract Crowdsale is Ownable { using SafeMath for uint; address multisig; uint restrictedPercent; address restricted; SimpleTokenCoin public token = new SimpleTokenCoin(); uint start; uint period; uint hardcap; uint rate; function Crowdsale() { multisig = 0xEA15Adb66DC92a4BbCcC8Bf32fd25E2e86a2A770; restricted = 0xb3eD172CC64839FB0C0Aa06aa129f402e994e7De; restrictedPercent = 40; rate = 100000000000000000000; start = 1500379200; period = 28; hardcap = 10000000000000000000000; } modifier saleIsOn() { require(now > start && now < start + period * 1 days); _; } modifier isUnderHardCap() { require(multisig.balance <= hardcap); _; } function finishMinting() public onlyOwner { uint issuedTokenSupply = token.totalSupply(); uint restrictedTokens = issuedTokenSupply.mul(restrictedPercent).div(100 - restrictedPercent); token.mint(restricted, restrictedTokens); token.finishMinting(); } function createTokens() isUnderHardCap saleIsOn payable { multisig.transfer(msg.value); uint tokens = rate.mul(msg.value).div(1 ether); uint bonusTokens = 0; if(now < start + (period * 1 days).div(4)) { bonusTokens = tokens.div(4); } else if(now >= start + (period * 1 days).div(4) && now < start + (period * 1 days).div(4).mul(2)) { bonusTokens = tokens.div(10); } else if(now >= start + (period * 1 days).div(4).mul(2) && now < start + (period * 1 days).div(4).mul(3)) { bonusTokens = tokens.div(20); } tokens += bonusTokens; token.mint(msg.sender, tokens); } function() external payable { createTokens(); } } |
Если вы посмотрите еще раз на бонусную программу Polybius и TenX, то увидите что они указывают периоды бонусов в днях. А у нас они указаны в математических терминах — в четвертях. Инвестору приятнее когда написано все человеческим языком — т.е. в днях. Поэтому вашим домашним заданием будет перевести расчет в дни.
В этом уроке мы ввели бонусную программу для инвесторов, что позволит нам мотивировать инвестора вложиться больше и быстрее. Теперь наш ICO полноценный!
Продолжение читать тут. Предыдущий урок тут.
Если у вас возникли вопросы то можете смело писать на электронную почту (раздел «контакты«). Также приветствуется критика.
Если статья показалась вам полезной и вы желаете отблагодарить автора, то это можно сделать отослав немного эфира на адрес 0xEA15Adb66DC92a4BbCcC8Bf32fd25E2e86a2A770.
Привет! Отличные и очень полезные статьи! Планируются ли какие-либо материалы по интеграции смарт-контрактов со сторонними платформами? Например, мне хотелось бы изучить момент внедрения UI для смарт-контракта в веб-сайт или мобильное приложение. Пока в интернетах мало доступной информации на эту тему.
Спасибо за лучший туториал по смарт-контрактам!
Это уже децентрализованные приложения — dapp. В планах конечно есть, но пока со временем беда. Поэтому не в ближайшем будущем. А так хочется посвятить этой теме отдельно курс.
Есть хорошая статья, на английском: https://medium.com/@mvmurthy/full-stack-hello-world-voting-ethereum-dapp-tutorial-part-2-30b3d335aa1f
…И еще одна поправка.
В «function createTokens()» в условиях пропущено «start +».
В результате — срабатывают только скидки 25% и 0%.
Исправил и добавил формализма для читабельности:
За статьи — спасибо! Ждем следующих серий! 🙂
Спасибо, поправил. По формализму — не могу сказать что ваш вариант более читабельный, поэтому пока оставлю свой.
Я вот наоборот, убрал формализма для читабельности и добавил одну переменную, saleTime. Получилось вот так:
В предыдущем комментарии код был изуродован местным скриптом.
Даю ссылку на гист: https://gist.github.com/denispeplin/1899bb35e552bd348376fdaf2bf76fb2
не подскажешь как узнать курс эфира к доллару на время начала ICO
На бирже bitter, например. Если речь только об узнать.
Нужно написать смарт-контракта.
Подробнее по работе и оплате по телефону: +79778192035
E-mail: atum314@gmail.com
Все предложения по почте. Тут не доска объявлений.
uint restrictedTokens = issuedTokenSupply.mul(restrictedPercent).div(100 — restrictedPercent);
может я чего то не понимаю но если мы в общем купили 100 токенов и наш процент как разработчиков 20 % то получиться такая формула
100*20/100-20=2000/80=25 а должно получиться 20 . Так вот вопрос зачем в скобках от 100 отнимаем процент ? если просто должны поделить на 100?
Мы указываем процент не от количества проданных токенов, а от общего количества токенов (включая свои бонусные)
скопировал код из данной темы и при попытке вызова fallback выдает вот это
transact to Crowdsale.(fallback) pending …
transact to Crowdsale.(fallback) errored: Error: gas required exceeds allowance or always failing transaction
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:1341136
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:918064
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:351880
at o (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:370111)
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:354710
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:351880
at u (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:354986)
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:355024
at Ht (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:364393)
at Object. (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:355113)
at e.value (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:918979)
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:918546
at n (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:370055)
at o (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:370133)
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:354710
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:918512
at n (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:370055)
at o (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:370133)
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:354710
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:918885
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:355520
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:351880
at u (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:354986)
at s (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:354908)
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:354710
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:355500
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:918767
at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:766706
at n (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/scripts/background.js:1:134148)
Залил этот контракт в testnet, все работает отлично, но эфир не приходит на адрес multising. В чем дело? кто нибудь может подсказать?