
Сумасшедший хайп вокруг криптовалют и всевозможных технологических стартапов подталкивает людей к тому, чтобы обращать на эти две темы повышенное внимание. Кого-то привлекает возможность кратно заработать в сжатые сроки, для других участие в венчурных проектах, использующих современные технологии – это возможность приобщиться к современным трендам и, возможно, даже похвастаться этим.
Но как и в истории с систематической ошибкой выжившего, нужно обращать внимание не только на истории успеха, которые постоянно приводят в пример, но и на истории, где люди потеряли кучу времени и денег, пытаясь развивать свой проект, написали тонны кода, а в итоге не получили ничего. Причем довольно часто потерянные деньги принадлежат инвесторам. Конечно, всем хотелось бы заработать на ранних инвестициях в компании типа facebook, но к сожалению 9 из 10 таких компаний прогорают.
Венчурное инвестирование — очень интересный вид бизнеса, но для большинства мелких частных инвесторов вход туда просто закрыт. Происходит это в основном потому, что ввиду колоссального риска при инвестициях в стартапы, инвестирование в один-два проекта смерти подобно. Поэтому даже самые маленькие венчурные фонды имеют в своем портфеле как минимум 20-30 проектов. Если хотя бы одна из их портфельных компаний выстрелит, это покроет расходы на остальные проекты. Но проекты не хотят брать от инвесторов 100 тысяч. Вложения в каждый из них составляют как минимум миллион. Простая арифметика показывает, что если у вас в кармане нет капитала в размере 15-20 миллионов, вы можете забыть об этом.
В то же время, есть и другая схема инвестирования в стартапы — краудфандинг. Вложить при этом вы можете сколько угодно, но разговаривать с вами как с инвестором никто не будет. Опять же, в большинстве случаев, стартап, собрав необходимое количество средств, расслабляется, так как не несет никакой ответственности перед мелкими инвесторами, которые вкладывают в него по сути на удачу. Стартапы рушатся не только из за лени и безответственности их начинателей, но и из за непредвиденных факторов, которые на старте предусмотреть практически невозможно.
С появлением эфириума и смарт-контрактов я заразился идеей написать систему, которая позволяла бы мелким инвесторам контролировать ход выполнения стартапов и тем самым повышать для себя процент прибыльных инвестиций. По сути это краудфандинговая платформа, но с одним существенным отличием — проект получает доступ не ко всем собранным средствам, а только к небольшой их части, а инвестор может в любой момент забрать неосвоенные средства, если проект не проходит заявленные на старте этапы. Гарантом этого будет выступать не система, а смарт-контракт.
Бизнес-модель самой платформы подразумевает, что она будет получать в качестве комиссии 5% собранного каждым проектом эфира (читай, живыми деньгами) и 5% акциями будущего предприятия. Сама платформа по сути является акционерным обществом, и вы можете стать ее владельцем, купив долю ее акций и получая в будущем эти самые 5+5 процентов, автоматическим становясь инвестором всех проектов, которые будут привлекать инвестиции на базе данной платформы.
Если бы такая система существовала, я бы сам с удовольствием вкладывался в нее саму, и в проекты, понимая, что я могу всегда выйти из них, если что-то по моему мнению пойдет не так. А раз такой системы нет, то я решил написать ее самостоятельно.
Далее в статье я буду разбирать «по косточкам» смарт-контракт на solidity, который является прототипом будущей системы.
Контракт залит по адресу https://etherscan.io/address/0x15797a704628907dd2622d3e5711d4ea62cd5072#code и имеет открытый код, что исключает возможность его подмены.
Я не буду в подробностях разбирать технические моменты, а остановлюсь только на существенных деталях, важных с точки зрения функционирования самого контракта. Если у кого-то возникнут вопросы, комментарии к коду я буду постепенно дополнять.
Статью на Хабр я написал потому, что вряд ли кто-то кроме людей, близких к ИТ, сможет оценить идею.
Как говорил кто-то из разработчиков, «words are bullshit, show me the code». Итак, поехали:
Куски кода с расширенными комментариями
pragma solidity ^0.4.19; library itMaps {..} - библиотека для хранения списков. contract ERC20 {..} - интерфейс, который должен быть реализован, чтобы акция могла обращаться на бирже contract TakeMyEther is ERC20{ // о чем мы договариваемся? .. uint private initialSupply = 2800000; // Всего выпускается столько акций uint public soldTokens = 0; //тут хранится число проданных акций .. address public TakeMyEtherTeamAddress; //Адрес команды itMaps.itMapAddressUint tokenBalances; //Реестр держателей акций платформы mapping (address => uint256) weiBalances; //Вспомогательная структура для хранения того, сколько денег потратил акционер. mapping (address => uint256) weiBalancesReturned; //Объем возвращенных средств акционеру, если он использовал вывод uint public percentsOfProjectComplete = 0; //Стадия реализации платформы uint public lastStageSubmitted; // Когда было объявлено о завершении очередной стадии uint public lastTimeWithdrawal; // Когда команда последний раз выводила средства на реализацию uint public constant softCapTokensAmount = 500000; //Минимальное количество акции при первичном размещении uint public constant hardCapTokensAmount = 2250000; //Максимальный объем реализуемых акций uint public constant lockDownPeriod = 1 weeks; // От момента объявления о завершении очередной стадии у инвестора есть неделя на то, чтобы очередная порция средств не была заблокирована с его счета uint public constant minimumStageDuration = 2 weeks; // Минимальное время не реализацию очередной стадии. bool public isICOfinalized = false; // Завершено ли первичное размещение акций bool public projectCompleted = false; // Завершен ли проект modifier onlyTeam { if (msg.sender == TakeMyEtherTeamAddress) { _; } } mapping (address => mapping (address => uint256)) allowed; event StageSubmitted(uint last); //Объявление о завершении очередной стадии проекта event etherPassedToTheTeam(uint weiAmount, uint when); // Средства переведены команде event etherWithdrawFromTheContract(address tokenHolder, uint numberOfTokensSoldBack, uint weiValue); // Возврат средств инвестору event Burned(address indexed from, uint amount); // Событие об уничтожении нереализованных акций event DividendsTransfered(address to, uint tokensAmount, uint weiAmount); // Перевод средств в качестве дивидендов держателям акций платформы ... /*Перевод акций от одного держателя другому. Если акции отправляются на адрес контракта, он возвращает вам все доступные неосвоенные на данный момент средства */ function transfer(address to, uint value) public returns (bool success) { if (tokenBalances.get(msg.sender) >= value && value > 0) { if (to == address(this)) { // if you send even 1 token back to the contract, it will return all available funds to you returnAllAvailableFunds(); return true; } else { return transferTokensAndEtherValue(msg.sender, to, value, getAverageTokenPrice(msg.sender) * value); } } else return false; } ... // Покупка акций: function () public payable { require (!projectCompleted); uint weiToSpend = msg.value; //recieved value uint currentPrice = getCurrentSellPrice(); //0.5 ETH or 1 ETH for 1000 tokens uint valueInWei = 0; uint valueToPass = 0; if (weiToSpend < currentPrice) {// return ETH back if nothing to buy return; } if (!tokenBalances.contains(msg.sender)) tokenBalances.insert(msg.sender, 0); if (soldTokens < softCapTokensAmount) { uint valueLeftForSoftCap = softCapTokensAmount - soldTokens; valueToPass = weiToSpend / currentPrice; if (valueToPass > valueLeftForSoftCap) valueToPass = valueLeftForSoftCap; valueInWei = valueToPass * currentPrice; weiToSpend -= valueInWei; soldTokens += valueToPass; weiBalances[address(this)] += valueInWei; transferTokensAndEtherValue(address(this), msg.sender, valueToPass, valueInWei); } currentPrice = getCurrentSellPrice(); //renew current price if (weiToSpend < currentPrice) { return; } if (soldTokens < hardCapTokensAmount && soldTokens >= softCapTokensAmount) { uint valueLeftForHardCap = hardCapTokensAmount - soldTokens; valueToPass = weiToSpend / currentPrice; if (valueToPass > valueLeftForHardCap) valueToPass = valueLeftForHardCap; valueInWei = valueToPass * currentPrice; weiToSpend -= valueInWei; soldTokens += valueToPass; weiBalances[address(this)] += valueInWei; transferTokensAndEtherValue(address(this), msg.sender, valueToPass, valueInWei); } if (weiToSpend / 10**17 > 1) { //return unspent funds if they are greater than 0.1 ETH msg.sender.transfer(weiToSpend); } } // Возврат неосвоенных средств акционеру function returnAllAvailableFunds() public { require (tokenBalances.contains(msg.sender)); //you need to be a tokenHolder require (!projectCompleted); //you can not return tokens after project is completed uint avPrice = getAverageTokenPrice(msg.sender); weiBalances[msg.sender] = getWeiAvailableToReturn(msg.sender); //depends on project completeness level uint amountOfTokensToReturn = weiBalances[msg.sender] / avPrice; require (amountOfTokensToReturn>0); uint valueInWei = weiBalances[msg.sender]; transferTokensAndEtherValue(msg.sender, address(this), amountOfTokensToReturn, valueInWei); emit etherWithdrawFromTheContract(msg.sender, amountOfTokensToReturn, valueInWei); weiBalances[address(this)] -= valueInWei; soldTokens -= amountOfTokensToReturn; msg.sender.transfer(valueInWei); } // View functions // Эти функции можно посмотреть на https://etherscan.io/address/0x15797a704628907dd2622d3e5711d4ea62cd5072#readContract Их назначение по сути очевидно // Team functions Что может сделать команда? //Завершить продажу своих акций function finalizeICO() public onlyTeam { require(!isICOfinalized); // this function can be called only once if (soldTokens < hardCapTokensAmount) require (lastStageSubmitted + minimumStageDuration < now); // ICO duration is at least 2 weeks require(soldTokens >= softCapTokensAmount); //means, that the softCap Reached uint tokensToPass = passTokensToTheTeam(); //but without weiValue, so the team can not withdraw ether by returning tokens to the contract burnUndistributedTokens(tokensToPass);//tokensToPass); // undistributed tokens are destroyed lastStageSubmitted = now; emit StageSubmitted(lastStageSubmitted); increaseProjectCompleteLevel(); // Now, team can withdraw 10% of funds raised to begin the project passFundsToTheTeam(); isICOfinalized = true; } // Объявить о завершении очередной стадии function submitNextStage() public onlyTeam returns (bool success) { if (lastStageSubmitted + minimumStageDuration > now) return false; //Team submitted the completeness of previous stage more then 2 weeks before. lastStageSubmitted = now; emit StageSubmitted(lastStageSubmitted); increaseProjectCompleteLevel(); return true; } /*Разблокировать очередную порцию финансов для реализации проекта через неделю после публикации завершения очередной стадии*/ function unlockFundsAndPassEther() public onlyTeam returns (bool success) { require (lastTimeWithdrawal<=lastStageSubmitted); if (lastStageSubmitted + lockDownPeriod > now) return false; //funds can not be passed until lockDownPeriod ends if (percentsOfProjectComplete == 100 && !projectCompleted) { projectCompleted = true; if (tokenBalances.get(address(this))>0) { uint toTransferAmount = tokenBalances.get(address(this)); tokenBalances.insert(TakeMyEtherTeamAddress, tokenBalances.get(address(this)) + tokenBalances.get(TakeMyEtherTeamAddress)); tokenBalances.insert(address(this), 0); emit Transfer(address(this), TakeMyEtherTeamAddress, toTransferAmount); } } passFundsToTheTeam(); return true; } // Receive dividends // Отправка дивидендов всем держателям акции платформы function topUpWithEtherAndTokensForHolders(address tokensContractAddress, uint tokensAmount) public payable { uint weiPerToken = msg.value / initialSupply; uint tokensPerToken = 100 * tokensAmount / initialSupply; //Multiplication for more precise amount uint weiAmountForHolder = 0; uint tokensForHolder = 0; for (uint i = 0; i< tokenBalances.size(); i += 1) { address tokenHolder = tokenBalances.getKeyByIndex(i); if (tokenBalances.get(tokenHolder)>0) { weiAmountForHolder = tokenBalances.get(tokenHolder)*weiPerToken; tokensForHolder = tokenBalances.get(tokenHolder) * tokensPerToken / 100; // Dividing because of the previous multiplication tokenHolder.transfer(weiAmountForHolder); //This will pass a certain amount of ether to TakeMyEther platform tokenHolders if (tokensContractAddress.call(bytes4(keccak256("authorizedTransfer(address,address,uint256)")), msg.sender, tokenHolder, tokensForHolder)) //This will pass a certain amount of tokens to TakeMyEther platform tokenHolders emit DividendsTransfered(tokenHolder, tokensForHolder, weiAmountForHolder); } } } function passUndistributedEther() public { require (projectCompleted); uint weiPerToken = (address(this).balance * 100) / initialSupply; for (uint i = 0; i< tokenBalances.size(); i += 1) { address tokenHolder = tokenBalances.getKeyByIndex(i); if (tokenBalances.get(tokenHolder)>0) { uint weiAmountForHolder = (tokenBalances.get(tokenHolder)*weiPerToken)/100; tokenHolder.transfer(weiAmountForHolder); //This will pass a certain amount of ether to TakeMyEther platform tokenHolders emit DividendsTransfered(tokenHolder, 0, weiAmountForHolder); } } } // When project is finished and Dividends are passed to the tokenHolders, there is some wei, left on the contract. Gradually, there can be a large amount of wei left, so it should be also distributed among tokenHolders. // Internal functions //Реализация внутренних функций: function transferTokensAndEtherValue(address from, address to, uint value, uint weiValue) internal returns (bool success){ if (tokenBalances.contains(from) && tokenBalances.get(from) >= value) { tokenBalances.insert(to, tokenBalances.get(to) + value); tokenBalances.insert(from, tokenBalances.get(from) - value); weiBalances[from] -= weiValue; weiBalances[to] += weiValue; emit Transfer(from, to, value); return true; } return false; } function passFundsToTheTeam() internal { uint weiAmount = getAvailableFundsForTheTeam(); TakeMyEtherTeamAddress.transfer(weiAmount); emit etherPassedToTheTeam(weiAmount, now); lastTimeWithdrawal = now; } function passTokensToTheTeam() internal returns (uint tokenAmount) { //This function passes tokens to the team without weiValue, so the team can not withdraw ether by returning tokens to the contract uint tokensToPass = getNumberOfTokensForTheTeam(); tokenBalances.insert(TakeMyEtherTeamAddress, tokensToPass); weiBalances[TakeMyEtherTeamAddress] = 0; // those tokens don't cost any ether emit Transfer(address(this), TakeMyEtherTeamAddress, tokensToPass); return tokensToPass; } function increaseProjectCompleteLevel() internal { if (percentsOfProjectComplete<60) percentsOfProjectComplete += 10; else percentsOfProjectComplete = 100; } function burnUndistributedTokens(uint tokensToPassToTheTeam) internal { uint toBurn = initialSupply - (tokensToPassToTheTeam + soldTokens); initialSupply -= toBurn; tokenBalances.insert(address(this), 0); emit Burned(address(this), toBurn); } }
И что?
Закономерный вопрос, который может возникнуть в конце прочтения заметки.
Основная цель, как я ее вижу, состоит из нескольких частей:
1) Информирование сообщества о возможностях для бизнеса, которые предоставляет блокчейн.
2) Ревью проекта и/или кода, получение обратной связи от сообщества, так как это один из лучших способов проверки гипотезы. Возможно я просто зря трачу время и подобные идеи никому не интересны?
3)
if (ответ на предыдущий вопрос == false) { Поиск (единомышленников); Поиск (инвесторов); Поиск (стартап-проектов); }
Спасибо за внимание, всем добра!
