Для экономии времени я написал контракт заранее. Давайте разберем его по шагам.
Смартконтракт является программой написанной на языке программирования. В нашем случае на языке Solidity. Для разработки простых контрактов я использую онлайн редактор и компилятор Remix.
Признаком хорошего тона считается начинать любую программу, в т.ч. и смартконтракт, с указания лицензии, на основе которой она распространяется. В нашем случае это GPL. Также можно указать себя в качестве автора контракта, конечно, если вы не пишете контракт для какого-нибудь скамового проекта, где стесняетесь указать себя в качестве автора.
/*
This file is part of the EasyCrowdsale Contract.
The EasyCrowdsale Contract is free software: you can redistribute it and/or
modify it under the terms of the GNU lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The EasyCrowdsale Contract is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU lesser General Public License for more details.
You should have received a copy of the GNU lesser General Public License
along with the EasyCrowdsale Contract. If not, see <http://www.gnu.org/licenses/>.
@author Ilya Svirin <i.svirin@prover.io>
*/
Сразу после идет строка, которая указывает, какую версию компиллятора следует использовать. Если этой строки не будет, то смартконтракт не скомпилируется.
pragma solidity ^0.4.0;
Далее идет непосредственно исходный код самого смартконтракта, который я структурировал в виде иерархии контрактов, каждый из которых реализует законченную функциональность. Это упрощает понимание и последующее использование кода в ваших контрактах.
Прежде всего следует понимать, что после загрузки смартконтракта в виртуальную машину Ethereum вы будете взаимодействовать с ним на общих основаниях, как и все остальные пользователи. Логично, что мы, как команда проекта, хотели бы находиться в привилегированных условиях, которые должны как минимум выражаться в том, что контракт должен сформировать именно на командные токены и конечно же отдал именно нам собранный эфир. Для этого контракт должен знать своего владельца и именно за это отвечает контракт «owned».
contract owned {
address public owner;
function owned() payable {
owner = msg.sender;
}
modifier onlyOwner {
require(owner == msg.sender);
_;
}
function changeOwner(address _owner) onlyOwner public {
owner = _owner;
}
}
Контракт «owned» содержит лишь одно публичное поле «owner», значение которого инициализируется в конструкторе значением поля «sender» глобальной структуры «msg», таким образом, изначально владельцем контракта становится тот, кто осуществил его деплой.
Логично предусмотреть возможность смены владельца на случай, если наш private key будет скомпрометирован, для этого предусмотрена функция «changeOwner», которая получает в качестве параметра адрес нового владельца. Следует обратить на модификатор «onlyOwner», который определен внутри этого же смартконтракта. Модификаторы представляют собой очень удобную конструкцию, позволяющую сгруппировать и присвоить название условиям вызова функций смартконтракта. Модификатор «onlyOwner» проверяет, что вызов функции осуществляется с адреса, который сохранен в поле «owner».
Контракт «owned» полностью работоспособен и предельно прост, однако несет в себе одну скрытую угрозу, ведь при вызове функции «changeOwner» мы можем по ошибке указать несуществующий адрес, а значит, потеряем контроль над контрактом. Чтобы исправить этот недостаток, достаточно ввести еще поле, назовем его «candidate», а при вызове функции «changeOwner» будем сохранять новое значение сначала в «candidate», а перемещать его в «owner» будем, как только кандидат подтвердит свое вступление в права, вызвав со своего адресу функцию «confirmOwner».
Следующий в иерархии контракт – «Crowdsale», отвечает непосредственно за сбор средств и выдачу токенов и наследует рассмотренный ранее контракт «owned».
contract Crowdsale is owned {
uint256 public totalSupply;
mapping (address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 value);
function Crowdsale() payable owned() {
totalSupply = 21000000;
balanceOf[this] = 20000000;
balanceOf[owner] = totalSupply - balanceOf[this];
Transfer(this, owner, balanceOf[owner]);
}
function () payable {
require(balanceOf[this] > 0);
uint256 tokensPerOneEther = 5000;
uint256 tokens = tokensPerOneEther * msg.value / 1000000000000000000;
if (tokens > balanceOf[this]) {
tokens = balanceOf[this];
uint valueWei = tokens * 1000000000000000000 / tokensPerOneEther;
msg.sender.transfer(msg.value - valueWei);
}
require(tokens > 0);
balanceOf[msg.sender] += tokens;
balanceOf[this] -= tokens;
Transfer(this, msg.sender, tokens);
}
}
Особое внимание следует обратить на следующие элементы контракта:
- Публичное поле «totalSupply», которое должно содержать общее количество токенов, выпущенных смартконтрактом;
- Публичная карта «balanceOf», которое содержит информацию о балансах всех держателей токенов;
- Событие Transfer, которое должно испускаться смартконтрактом при каждой операции перемещения токенов между держателями токенов.
Все эти три элемента объединяет одно, они являются обязательной частью стандарта ERC20, который необходимо соблюдать, чтобы информация о наших токенах корректно отображалась в кошельках пользователей и etherscan.io.
Конструктор смартконтракта «Crowdsale» предельно прост. Прежде всего инициализируется значение поля «totalSupply». Наш контракт выпускает 21 миллион токенов, из которых 20 миллионов сразу будут перемещены на баланс смартконтракта. Будем считать, что токены с адреса смартконтракта как раз и доступны для продажи. Оставшиеся токены, в нашем случае 1 миллион, будут записаны на адрес владельца контракта. Ну и в конце конструктора испускается событие «Transfer», которое помещается в блокчейн и информирует пользователей контракта о том, что с баланса контракта на баланс владельца контракта переведено соответствующее количество токенов. Именно испускание этого события позволит etherscan.io корректно отобразить держателей токенов и их балансы.
Ну и самая главная функция смартконтракта «Crowdsale», так называемая payback функция, которая вызывается каждый раз, когда эфир поступает на адрес нашего смартконтракта. В самом начале осуществляется проверка, что на балансе смартконтракта есть хоть какое-то количество токенов для продажи. Далее устанавливаем фиксированную цену токенов – 5000 штук за 1 эфир. Затем вычисляем, сколько токенов необходимо отправить отправителю эфира. Количество переданных в транзакции средств записано в поле «value» глобальной структуры «msg» и указано оно в «wei», поэтому при определении количества токенов осуществляем перевод «wei» в «ether».
Затем осуществляется проверка того, что на балансе смартконтракта есть достаточное количество токенов для продажи. Если запрошено больше токенов, чем есть у смартконтракта, то будем переводить все оставшиеся токены. Определяем стоимость в «wei» оставшихся токенов и возвращаем отправителю излишне переведенный эфир. Убеждаемся, что количество покупаемых токенов ненулевое, после чего записываем это количество токенов на баланс покупателя и списываем их с баланса смартконтракта. В конце не забываем испустить событие Transfer.
На этом собственно реализация функциональности сбора средств закончена, но теперь нужно сделать наш токен операбельным и реализовать еще некоторые функции стандарта ERC20. Это сделано в контракте «EasyToken», который наследуется от рассмотренного ранее контракта «Crowdsale».
contract EasyToken is Crowdsale {
string public standard = 'Token 0.1';
string public name = 'EasyTokens';
string public symbol = "ETN";
uint8 public decimals = 0;
function EasyToken() payable Crowdsale() {}
function transfer(address _to, uint256 _value) public {
require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
Transfer(msg.sender, _to, _value);
}
}
Прежде всего определим 4 публичных поля, которые сейчас практически не используются никакими кошельками, но для порядка все же определим их. Здесь укажем полное и сокращенное наименование токена, а также количество дробных знаков. В нашем случае токен является неделимым, т.к. значение поля «decimals» установлено равным 0.
И наконец, единственной функцией смартконтракта «EasyToken», ради которой мы создавали этот контракт, является «transfer», которая также является частью стандарта ERC20 и позволит кошелькам пользователей осуществлять передачу токенов друг другу, переводить их на биржу и выводить их с нее. Реализация функции крайне проста, проверяется достаточность количества токенов на балансе отправителя, после чего баланс отправителя уменьшается, а баланс получателя увеличивается на запрошенное количество токенов. В конце испускается событие «Transfer». Теперь наш токен является операбельным и осталось сделать самое главное – предоставить владельцу контракта возможность вывести собранные эфиры. Это мы сделаем в контракте «EasyCrowdsale».
contract EasyCrowdsale is EasyToken {
function EasyCrowdsale() payable EasyToken() {}
function withdraw() public onlyOwner {
owner.transfer(this.balance);
}
}
Функция «withdraw» имеет модификатор «onlyOwner», т.е. может быть вызвана только владельцем смартконтракта. Единственное, что она делает – переводит весь баланс смартконтракта на адрес владельца смартконтракта.
Несмотря на то, что рассмотренный нами смартконтракт является полностью функционально законченным и работоспособным, использовать его в реальном проекте я бы не рекомендовал. Чрезмерное упрощение логики контракта привело к тому, что такой контракт не обеспечивает защиту интересов инвесторов в должной мере, а именно, не устанавливает срок проведения crowdsale, не устанавливает минимальной границы сбора, не возвращает средства инвесторам в случае недостижения минимальной границы, а также содержит ряд известных уязвимостей на уровне компилятора языка Solidity, например, подвержен так называемой short address attack.
Многие из этих недостатков устранены в смартконтракте нашего проекта PROVER. Контракт PROOF загружен в Ethereum по этому адресу вместе с исходным кодом, с которым можно познакомиться. Можно даже проверить, как работает контракт, отправив на него реальный эфир, у нас как раз сейчас идет Pre-ICO:). На самом деле, мы будем рады, если вы присоединитесь к presale нашего проекта PROVER, который продлится до конца сентября. PROVER – это уникальная технология подтверждения подлинности видеоматериалов на базе блокчейн и видеоаналитики.
Также приглашаю пройти пререгистрацию в еще одном моем проекте — OpenLongevity — проект, выступающий инициатором, организатором и гарантом открытости клинических исследований терапий старения. Мы включим самих пациентов в глобальное движение по поиску и тестированию потенциальных терапий старения, которые, доказав свою эффективность, сразу же станут частью их собственной жизни.
Полезные ссылки:
- Полный код разобранного в данном посте смартконтракта
- Моя видеопрезентация по созданию простого смартконтракта для ICO
- ERC20 Token standard
- Мой проект OpenLongevity — открыта регистрация для участия в Pre-ICO!
- Мой проект PROVER — идет Pre-ICO!!!
- Еще пост про мой проект PROVER
Комментарии (80)
sirocco
17.09.2017 09:16У меня другая беда. Есть кошелёк Mist, и закинул я туда токенов plbt. И теперь никак не пойму как их вывести оттуда, нигде не нашёл этой информации. Вообще одна головная боль с этими эфировскими кошельками.
riartem
17.09.2017 16:57Добавьте в кошелёк смарт-контракт валюты PLBT.
Вкладка «Contracts», раздел «Custom tokens», кнопка "+ Watch token"
Данные для добавления (взято из п.23 FAQ на сайте Polybius)0x0AfFa06e7Fbe5bC9a764C979aA66E8256A631f02
Token name: Polybius Token
decimals: 6
symbol: PLBTsirocco
17.09.2017 19:24Нет. Он требует ещё что-то прописать в графе JSON ИНТЕРФЕЙС.
riartem
17.09.2017 17:06Технически кастомные токены на базе эфира никуда не передаются и сами не могут потеряться (главное не терять доступ к аккаунту с эфиром, на который эти токены «отправляли»).
Это просто цифра внутри контракта, которая говорит, что адресу X принадлежит Y токенов. Поэтому переживать не о чем.isvirin Автор
17.09.2017 18:16Собственно, контракт, рассмотренный в этом посте, наглядно подтверждает этот тезис:)
mayorovp
17.09.2017 09:47+1а также содержит ряд известных уязвимостей на уровне компилятора языка Solidity, например, подвержен так называемой short address attack
ЁПРСТ! Вы хотите сказать, что существует уязвимость компилятора, которая делает нормальные с виду контракты уязвимыми — и она до сих пор не закрыта?
izzholtik
17.09.2017 11:54+2Всё это сильно напоминает мир 1С, в котором люди, работающие с деньгами, зачастую не знают основ программирования и лепят код "на пофиг".
isvirin Автор
17.09.2017 12:07Этот пост как раз для тех, кто не хочет заморачиваться:)
При написании нормального смартконтракта нужно думать о куче дополнительных вещей, о которых не всегда думают при обычном программировании: стоимость выполнения тех или иных операций (выраженная в деньгах), максимальные ограничения на стоимость в рамках одной транзакции (чтобы контракт не заблокировался), ну и куча ошибок компиллятора, которые надо знать и на уровне кода от них защищаться. Об этом, возможно, еще напишу другие посты.lany
17.09.2017 13:02стоимость выполнения тех или иных операций (выраженная в деньгах)
В идеале об этом должен компилятор думать. Он должен это измерять, компилировать, оптимизируя именно эту стоимость, выдавать вам советы в виде предупреждений и т. д. В светлом будущем, наверно, так и будет :-)
Alesh
17.09.2017 13:59Кстати, что посоветуете почитать для понимания основ?
isvirin Автор
17.09.2017 14:40Для понимания основ достаточно вот таких постов, как этот.
Достаточно системно рассматриваются примеры на официальном сайте ethereum.org.
Если программируете свободно на других языках, то можно попробовать такой формат — learnxinyminutes.com/docs/solidity.
Ну а перед сном надо читать вот это — solidity.readthedocs.io/en/develop/contracts.htmlriartem
18.09.2017 11:01Но я не увидел в вашем примере защиты от вышеупомянутой «short address attack» в методе transfer. Там разве не должно быть проверки на полноту адреса? (Или я неправильно понял суть атаки?)
isvirin Автор
18.09.2017 11:02В моем посте ее и нет, чтобы не усложнять понимание контракта. Суть Вы поняли совершенно правильно, действительно должна быть проверка на полноту адреса, обычно добавляют соответствующий модификатор.
vz10
17.09.2017 10:44Вопрос по существу, а зачем мы все время наследуем контракты? То есть на выходе у нас будет несколько разных замайненных контрактов или это просто синтаксический сахар для модульности и контракт на выходи будет один?
isvirin Автор
17.09.2017 11:09+1Наследование в данном примере исключительно ради структурирования кода, чтобы было удобнее дергать законченными кусками в свои контракты. Деплоить надо только EasyCrowdsale, хотя чисто технически можно деплоить любой из рассмотренных контрактов.
Serdonda
17.09.2017 12:12Контракт «owned» полностью работоспособен и предельно прост, однако несет в себе одну скрытую угрозу, ведь при вызове функции «changeOwner» мы можем по ошибке указать несуществующий адрес, а значит, потеряем контроль над контрактом
А как у Вас проверяются «полномочия» нового владельца? Насколько я понимаю в вашем варианте любой желающий может использовать функцию «changeOwner» и стать новым владельцем. Или это не так?isvirin Автор
17.09.2017 12:17Функцию changeOwner может вызвать только текущий владелец. Обратите внимание на модификатор onlyOwner у этой функции.
Serdonda
17.09.2017 12:25но вы говорите о том что эта функция предназначена для
смены владельца на случай, если наш private key будет скомпромитирован,
Скомпрометирован — значит стал известен посторонним/утерян/и т.д…
Таким образом тот кому стал известен private key меняет владельца на свой адрес. В чем тогда смысл этой функции? Видится что только в гонке — кто быстрее сменит адрес владельца — настоящий владелец или посторонний которому стал известен private keylany
17.09.2017 13:06+1Бывают ситуации, когда вы не уверены, скомпрометирован ключ или нет. К примеру, машина с ключом была заражена вирусом или доступ к ней получил посторонний. В такой ситуации лучше перестраховаться и перенести все привязанные контракты на другой ключ. В принципе да, именно в гонке и смысл. Злоумышленник мог не целиться прямо на ключ, а просто получить всю информацию из взломанной системы и неспеша с ней разбираться. Тогда есть все шансы его опередить.
mayorovp
17.09.2017 13:26+1Скомпрометирован не означает "украден". Предполагается, что у владельца есть в запасе какое-то время.
isvirin Автор
17.09.2017 14:01Можно предложить и более параноидальную схему по принципу multisig, когда контракт «знает» нескольких владельцев, а для смены одного из владельцев требуется confirm всех остальных. Реализовать это несложно, но это выходит за рамки «простого смартконтракта для ICO» :)
edogs
17.09.2017 13:51-2Также можно указать себя в качестве автора контракта, конечно, если вы не пишете контракт для какого-нибудь скамового проекта, где стесняетесь указать себя в качестве автора.
Вот это просто убило.
/sarcasm/
Разумеется, если ты не указываешь себя как автор, то это только потому что проект скамовый. Не по какой-либо другой причине. Анонимность вообще зло. Даешь доступ в интернет по паспорту. Криптовалюты ведь и созданы для однозначной идентификации пользователей. Открой все данные о себе, ведь тебе нечего скрывать, а если не открываешь, то значит ты занимаешься скамом и твое место тюрьма.
/sarcasm/
Нет, понятно, что мы overreacting, но блин, именно фразы вида «если стесняешься указать себя в качестве автора, значит тебе есть что скрывать, значит ты пишешь скамовый проект» и есть тот ручеек, с которого начинается та самая река. И особенно цинично это звучит в проектах связанных с криптой.isvirin Автор
17.09.2017 14:06Ощущается явная проблема с чувством юмора:) Прошу прощения, что оскорбил Ваши светлые чувства:)
На самом деле, рассмотренный смартконтракт в чистом виде скам, т.к. объективно никак не защищает интересов инвесторов (покупателей токенов). Такие контракты не должны быть в боевых проектах. Он всего лишь демонстрирует принципы организации краудсейла с технической точки зрения.
Касательно анонимности и ICO. Не открою америку, если скажу, что и в классическом венчуре и в ICO деньги дают прежде всего команде. Успешные анонимные ICO — большая редкость. Поэтому если вы не делаете скам (в проекте, в смартконтракте), то напишите, кто Вы есть такой, чтобы инвесторы видели, что Вы готовы ответить за базар:) Но это лишь мое мнение, возможно, я идеализирую. Но в нашим проектах PROVER и OpenLongevity мы руководствуемся именно такими принципами.edogs
17.09.2017 19:26-2Ощущается явная проблема с чувством юмора:)
Выбор темы для шуток лучше характеризует человека, чем его серьезные речи:)
Поэтому если вы не делаете скам (в проекте, в смартконтракте), то напишите, кто Вы есть такой, чтобы инвесторы видели, что Вы готовы ответить за базар:)
На самом деле это не дает никакой «ответственности за базар», Вы путаете направление подтверждения:)
Подпись в ico нужна только если Вы хотите доказать что это Ваш ico.
А вот ответственность за него Вы будете нести только если Вы вне ico подтвердите что это Ваш ico, подпись в ico для ответственности за базар не нужна.
Упрощенно говоря если в ico указаны данные Васи Пупкина, то с Васи Пупкина на этом основании Вы ни копейки не стрясете. Это с одной стороны.
С другой стороны если Вася Пупкин где-то вне ico признался что это его ico, то Вы и без подписи в ico сможете стрясти с него копеечку.isvirin Автор
17.09.2017 19:30Сразу видно, что ICO Вы не проводили:) Поговорите с юристами, они объяснят, что означает указание данных Васи Пупкина для Васи Пупкина при проведении ICO, кто несет ответственность и перед кем. Это далеко за границами этого поста, я принципиально не хочу выходить за технические рамки, хотя мы и обладаем глубокими юридическими компетенциями в данном вопросе.
edogs
18.09.2017 05:53Указание данных Васи Пупкина неизвестным лицом где бы то ни было не накладывает на Васю Пупкина никаких обязательств.
Urn
17.09.2017 16:24Не упел углубиться еще в код ваших контрактов… Но в чем преимущество по сравнению с уже написаными и оттестироваными реализациями ERC20, например github.com/ConsenSys/Tokens/tree/master/contracts?
isvirin Автор
17.09.2017 18:12ERC20 — это всего лишь интерфейс, реализация которого зависит от требуемой для конкретного проекта бизнес-логики.
Ну и контракты по ссылке не стоит считать эталоном — например, они подвержены той же short address attack.
Вот мой контракт проекта PROVER действительно прекрасен, но он реализует вполне конкретную бизнес-логику, требуемую именно для моего проекта.
isvirin Автор
17.09.2017 18:19+1Господа, которые минусуют пост и карму, вы в комментариях намекните, что не нравится, чтобы можно учесть в следующих постах:)
aleks_raiden
17.09.2017 18:36+1Еще лучше сразу реализовать ERC223 — как раз поля Symbol/Short name там есть.
igordata
17.09.2017 18:53+1А как обновить контракт если что-то нашлось потом критическое?
isvirin Автор
17.09.2017 19:05+1Контракт обновить нельзя, но можно загрузить новый и перевести туда всех держателей токенов. Для миграции можно предусмотреть дополнительную функциональность контракта, чтобы не делать это руками. Ну и в стиле децентрализации/блокчейн делать эту миграцию голосованием держателей токенов. Опять же хочу в качестве примера такой реализации привести свой «контракт PROVER»:https://etherscan.io/address/0x5B5d8A8A732A3c73fF0fB6980880Ef399ecaf72E#code. Там можно подсмотреть реализацию миграции НА этот контракт и С этого контракта. Все демократично и безопасно:)
isvirin Автор
17.09.2017 19:07Еще хорошей практикой является реализовывать сложную бизнес-логику в виде набора связанных между собой контрактов, причем отдельные части этой логики можно заменять, опять же голосованием держателей токенов. В блокчейне принято «играть» в демократию:)
spread
17.09.2017 22:02Не пойму как можно увидеть\вызвать, например в MIST, анонимную функцию после конструктора Crowdsale()
```contract Crowdsale is owned {
uint256 public totalSupply;
mapping (address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 value);
function Crowdsale() payable owned() {
totalSupply = 21000000;
balanceOf[this] = 20000000;
balanceOf[owner] = totalSupply — balanceOf[this];
Transfer(this, owner, balanceOf[owner]);
}
function () payable {
```
?isvirin Автор
17.09.2017 22:03Нету ее в Mist-е, а для того, чтобы она вызвалась, достаточно перевести эфир на адрес смартконтракта.
spread
17.09.2017 22:11А почему 1 параметер в owner.transfer(this.balance);
contract EasyCrowdsale is EasyToken {
function EasyCrowdsale() payable EasyToken() {}
function withdraw() public onlyOwner {
owner.transfer(this.balance);
}
}
ИМХО должен быть первым адрес кошелька куда владелец отправит\снимет токены? как определено выше
function transfer(address _to, uint256 _value) public {…
spread
17.09.2017 22:35-2А если юзер хочет вывести на свой холодный кошелёк свои EasyToken правильно так?
function withdrawToUserWallet(address _to, uint256 _value) public returns(bool success){
balanceOf[msg.sender] > _value;
return msg.sender.transfer(_to, _value);
}aleks_raiden
17.09.2017 23:21Как я понимаю, на холодный кошелек выводиться сам аккаунт ефирный — балансы токенов хранятся ТОЛЬКО внутри контрактов. Так что только владелец кошелька имеет доступ сразу ко всем активам внутри сети, а не по отдельности к токенам.
isvirin Автор
17.09.2017 23:35Если эфир был переведен на адрес смартконтракта с холодного кошелька, токены будут начислены на адрес этого холодного кошелька. Никаких дополнительных функций для этого не нужно.
mayorovp
18.09.2017 08:34Так холодный кошелек же на то и холодный что он должен находиться в оффлайне до востребования.
knott
18.09.2017 01:50Я конечно не эксперт, но не подвержен ли ваш контракт проблемам с reentrancy?
Перевод эфира должен вызвать такой же обработчик по умолчанию на
sender
, который в ответ может передать еще эфира контракту ICO, вызвав тем самым обработчик по умолчанию...isvirin Автор
18.09.2017 01:57Не вижу, где может возникнуть, описанная вами ситуация. Перевод эфира на рассмотренный контракт приведет к вызову payback функции, которая отгрузит токены. В ответ на отгрузку токенов у sender-а ничего не вызовется, т.к. это по сути внутреннее дело нашего контракта.
mayorovp
18.09.2017 08:45Насколько я понял, речь шла о вот этой строчке:
msg.sender.transfer(msg.value - valueWei);
Такая ситуация может возникнуть например вот в каком случае. Рассмотрим немного модифицированный контракт, который умеет делать withdraw не только владельцу — но и на любой указанный владельцем адрес. И предположим, что владелец того контракта решил сделать withdraw на адрес нашего (не спрашивайте зачем, допустим это была ошибка). При этом суммарная переведенная сумма оказалась больше чем у нас оставалось токенов.
В итоге делается посылка "сдачи" обратно отправителю, что интерпретируется тем контрактом как покупка токенов, но поскольку токены там уже закончились, сумма возвращается обратно к нам.
Что произойдет в таком случае? Это будет цикл или рекурсия?
isvirin Автор
18.09.2017 10:13Каждая транзакция стоит газа. Наш контракт все операции делает за счет газа sender-а и неважно, просто это кошелек или другой смартконтракт. Так что рекурсия эта быстро кончится без ущерба для нашего контракта.
mayorovp
18.09.2017 10:26То есть все-таки рекурсия, а не цикл?
Но тогда это можно попробовать превратить в атаку, за счет того что
balanceOf[this]
меняется уже после перевода "сдачи". Например, если в такой смартконтракт добавить миграцию на новую версию с голосованием токенами, злоумышленник может купить себе количество токенов, достаточное для "кворума", и сменить контракт на свой.
quantum
18.09.2017 09:44+1Основной функционал токенов уже реализован в zeppelin solidity и проверен многими людьми. Рекомендую наследоваться от их токенов.
Также пр разработке посмотрите на фреймворк truffle, позволяет запускать юниттесты
isvirin Автор
18.09.2017 10:08Юнит-тесты крайне полезная штука, достойны отдельного поста. Только использовать truffle для этого вовсе не обязательно.
lany
Смотрю с точки зрения обычного программиста. В ваших смартконтрактах реально принято код загружать такими константами вида
1000000000000000000
? Ведь это какое-то наивное детское программирование, хоть и называется громким словом solidity. Это неправильно на кучу слоёв.Слой 1. Можно легко пропустить где-нибудь нолик или неправильно их сосчитать и неправильно понять код. Язык должен позволять писать
1_000_000_000_000_000_000
. Если позволяет, программисты должны этим пользоваться.Слой 2. Это всё равно неправильно, потому что вероятность ошибки с пропуском нолика существует до сих пор. Должна существовать предопределённая именованная константа вроде
WEI_PER_ETHER = 1_000_000_000_000_000_000
, и в коде должна использоваться только она. Тогда станет понятнее, что происходит и вероятность ошибки ещё снизится.Слой 3. И это всё равно неправильно, потому что можно неправильно сделать вычисления. Например, умножить там, где надо разделить, передав не 10-18, а 10+18 попугаев. Нужны предопределённые функции или макросы с понятными именами вроде
convertWeiToEther(weiAmount)
иconvertEtherToWei(etherAmount)
.Слой 4. И это всё равно неправильно, потому что можно по ошибке забыть вызвать макрос. Должна быть объектная типизированная модель, исключающая возможность присваивания неправильного значения. Что-нибудь вроде
WEI.amount(uint256 value)
возвращает объект типаAmount
, и чтобы получить сумму в эфире, надо вызватьWEI.amount(value).toEther()
. При этом не следует использовать сырые значения в числах слишком часто, только в исключительных случаях вроде окончательного обмена данными с внешним источником. В идеале это должно быть спрятано в методах родительских контрактов и всякиеmsg.value
уже должны иметь типAmount
и писать надоmsg.value.subtract(MY_TOKEN.amount(tokens))
, где константаCurrency MY_TOKEN = createStableCurrency(Ether.amount(1/5000))
. Я придерживаюсь синтаксиса джавы, но это необязательно. Язык может и перегрузку операторов поддерживать, тогда можно писатьmsg.value - MY_TOKEN.amount(tokens)
, но при этом компиляция не пролезет, если попытаетесь эфир вычесть из токенов или сложить токены с веями.Пока всё выглядит так, что детям дали бомбой поиграться. И не надо рассказывать мне про стоимость исполнения на Etherium VM. Хороший оптимизирующий компилятор можно выкинуть все эти слои абстракций, сгенерировав точно такой же конечный код.
superhackkiller1997
Это уродство и никто так писать не должен. Если вам это нравиться — так и пишите, но про «должны» — забудьте.
Зачем вообще жить, если можно что-то сделать неправильно? Неважно, какой разрядности числа — всё это теряется и от разрядности ничего не зависит. Это просто рассуждения человека, который не привык к логике за пределами плавучки.
Это тот же самый ЖС, где типизация тут прикручена сбоку. А такой модели нет в и жаве.
И что же это изменит? Мне это в рамках value как-то помешает ошибиться?
Ну и самое главное, что никакой «суммы в эфире» — нет. Автор сам настроил себе абстракций и сам же их и должен поддерживать. Т.е. WEI.amount(value).toEther() == value по определению. Тут прослеживается, опять же, узость мышления и вас и автора, которые не могут мыслить за рамками плавучки.
Это пустые рассуждения. Есть уровень языка, а есть уровень рантайма. Это общая проблема людей из мира жавы и иже с ним, которые не отличают язык от рантайма.
Есть язык, не важно какой, он умеет только складывать чиселки. Есть некая система(ниже языка), которая экспортирует api в язык. Естественно, что это api существует в рамках «ниже языка» и уже поверх него нагромождаются всякие абстракции. И неважно кто поставщик этих абстракций — вендор языка, либо кто-либо ещё. Всё это существует за рамками языка.
Жава не поддерживает, как и 99% языков. Что дальше?
Опять же — попытка пихать и рассуждать в придуманных вами абстракциях на уровне «языка». Язык он про сложение чиселок. Так везде. В жаве нет логики «нельзя сложить рубли с доллары» — потому что это логика внешнего уровня.
Точно так же и тут. Токены — это логика внешнего уровня — на уровне языка/платформы их не существует. Точно так же как на уровне жавы не существует рублей. И почему-то вы не идёте и не жалуетесь на это.
Такого компилятора у жавы нет, как и у 99% языков. Что же с этим поделать? Откуда он должен взяться тут?
Ну и самое главное, как я уже говорил. Это мир жаваскрипта( и этот «язык» — это жаваскрипт) — там люди не знают о типизации, компиляторах и прочем. Хипстерский мир вообще компилятор увидел только с релизом ллвм. Точно так же, как в мире жавы не знают о компилтайме и мощной типизации, как это знают в том же мире С++.
mayorovp
Но в "жаву" такую логику можно при желании добавить. А тут — либо нельзя, либо можно, но как нам не рассказали. И на остальные ваши рассуждения — тот же самый ответ.
superhackkiller1997
Сюда так же.
Классы есть, типы есть, перегрузка есть. Вроде как всё необходимое есть, а уже далее — дело техники.
Да не тот же. Автор(комментария) предлагает взять из копеек сделать рубли в рамках целых чисел. Он предлагает делать из переменной как минимум пару, а это уже усложнение операций( что явно противоречит его концепции «компилятор все поправит»).
По мне так автор просто пытается натянуть свои представления из мира плавучки на данную ситуацию.
А проблема решается просто — не надо вводить новых сущностей. Это в реальном мире есть рубли и копейки, а в данном мире рублей нет.
Поэтому всё решается очень просто. Есть копейки. Есть цена в копейках за цент(токен). И всё сразу стало ясно и понятно.
Объясню ситуацию ещё проще. У автора была задача продать центы за копейки. Он ввёл для себя доллары, рубли. Исчисляет цены в них. Потом переводит значения одних в доллары, других в рубли, потом считает и потом переводит обратно. Вопрос — зачем?
lany
Где я это предлагал? Я предлагал не работать с голыми числами вообще, ни с целыми, ни с дробными. Их надо видеть только в точках взаимодействия с внешними системами. А там уже тип зависит от того, который навязан внешней системой. Я ничего не говорил ни про целые, ни про дробные, ни про пару. Объект класса Amount может всё внутри переводить в ваши любимые вэи, может использовать дробные числа, может хранить пару, это без разницы. В этом суть абстракции. Завтра точности вэев станет недостаточно, поделят их ещё в тысячу раз и что тогда? Если у вас код абстрагирован от представления, поменять придётся только один класс.
superhackkiller1997
Каноничный пример того, как «я не я и шкура не моя».
Нет, предполагалась логика именно плавучки и не иначе. Абстракция вот так взять и поменялось логику работы с данными/операциями. И попрошу я вас описать «как» и вы ничего не ответите.
Не верно. В рамках зерокост абстракций( а именно их вы определили) тип очень даже влияет на абстракции.
Не верно. Типичный пример манипуляций, когда говорим одно, а после делаем вид, что говорили другое.
Вы говорили не просто об абстракциях, а о зерокост абстракциях. Вот тут:
А теперь как удобно получается. Уже завелась шарманка об «а если», «а поменять один класс». Об этом вы изначально не говорили, а переобувание на ходу — любимый приём болтунов.
lany
Вы таки считаете, что лучше знаете, что у меня в голове происходит, чем я сам? Боюсь, спорить и в чём-то не соглашаться с человеком, который так считает, абсолютно бессмысленно. Поэтому продолжать не буду.
superhackkiller1997
Глупая попытка. Никто и нигде верить вас не должен. Именно поэтому ваши «я считал так, а не иначе» — никого не волнуют. Вы пишите то, что пишите. И это интерпретирую так, как интерпретирую. Если вы не согласны — аргументируйте. «я так не думаю/ как вы знаете что я имел ввиду» — это глупо и это не работает.
Так бы в мире ничего не работало. Сказал одно, а потом «я не то имел ввиду».
А поводу плавучки — вот доказательства: habrahabr.ru/post/338084/#comment_10419964
lany
Во всех языках мира придуманы разделители групп разрядов. Где-то апостроф сверху, где-то запятая снизу, где-то пробел. Это сделали не потому что кому-то нравится, а потому что это снижает вероятность ошибки. Подчерки лишь пример синтаксиса, он может быть любым, но вероятность ошибки должен снижать.
Выражения про плавучку абсолютно не понимаю. При чём здесь какая-то плавучка? Я говорю, численное представление суммы должно быть абстрагировано. Неважно, что там внутри. А вы мне про какую-то плавучку. Я и слов-то таких не употребляю никогда.
Яркий пример, когда хочется что-то сказать, а сказать нечего. Перегрузку операторов поддерживают многие языки. Из популярных больше поддерживают, чем не поддерживают. Да и суть не в этом, можно и
.subtract
писать, это не сильно хуже.Какое-то у вас примитивное представление о программировании. Либо вы на ассемблере всю жизнь пишете, тогда простительно.
Джавовый JIT (Hotspot C2) вполне способен на такие оптимизации. Я полагаю, Clang с кодом на C++ легко разделается тоже. Тут вполне тривиальная цепочка оптимизаций инлайнинга, эскейп-анализа и констант-фолдинга. Это всё в теории компиляции известно десятилетиями. Не знаю, что там в солидити, но учитывая, какие деньги крутятся в этериуме, хорошего компиляторщика нанять не должно быть большой проблемой.
Смысл последнего абзаца в том, что этериум = джаваскрипт и ничего с этим не поделаешь? Я думаю, в основу платформы никакая типизация не заложена и на ней вполне можно сделать нормальный язык, компилируемый в этериум. Либо он уже существует, просто автор этой статьи не умеет им пользоваться.
isvirin Автор
Друзья, все это не предмет данного поста. На низком уровне есть вполне себе читаемый байткод, при желании можно писать сразу в нем:) А можно создавать любые высокоуровневые абстракции. Сейчас есть два языка «высокого уровня» для написания смартконтрактов для Ethereum Virtual Machine, это Solidity (на основе Javascript) и Serpent (на основе Python). Второй скорее мертв, чем жив, т.к. уже больше года не было обновлений, а в Solidity фиксят регулярно ошибки компилятора. В настоящее время мы своей командой ведем работу над созданием визуального языка программирования смартконтрактов на основе Scratch. Пока еще окончательно не определились, будем ли сразу генерировать байткод или же в качестве промежуточного слоя будем использовать Solidity, чтобы оставить возможность верификации полученного кода. Вобщем, платформа Ethereum достаточно универсальна, чтобы решать на ней широкий класс задач. Вопрос в том, что у разработчиков не дошли руки сделать удобной прикладную разработку.
superhackkiller1997
Очередная манипуляция. Вам говорили не о какой-то там фиче в каких-то там языках, а ваших заявлениях на тему «программисты ДОЛЖНЫ использовать это».
Вот именно про «должны» и отвечайте.
Притом, вся ваша «логика» и ответы — это «пальцем в небо» и не более того. Я уже говорил о том, что это уровень языка, а не уровень ваших абстракций. Вы же это, благополучно, проигнорировали.
Вы пытаетесь требовать с языка и платформы интерфейсов для логики внешнего мира. Это то же самое, что с жавы требовать наличие абстракций для рублей. Ваши попытки изначально некорректны.
А по поводу плавучки — это прямо выводится из ваших предложений, ведь только в рамках неё рождается подобная логика. В противном случае — она бы просто не родилась бы.
Ярки пример пустой болтовни и глупых ссылок.
В верхней строке мы видим одну хипстоту, в половине которой вообще ваш синтаксис не работает и нет типизации. Т.е. мимо. В нижней строке из живого и типизированного — это кресты, сишарп — всё.
Где оно работает? В луа? Без типов и через жопу? Удачи сравнить веи с эфирами. Что там ещё. Пистон? К тому же, это всё рантайм и никакого зерокост.
Таким образом из всех языков подобное могут только кресты, скорее всего шарп/раст, но раст пока не взлетел.
И того — два языка. Скорее всего даже шарп не подойдёт и того — полтора. Один живой и второй не взлетевший.
Ну дак покажите мне операции уровня языка за рамками сложения чиселок. Это же просто и без болтовни.
Не способен. Причина проста — жава просто неспособна на подобные абстракции.
Я за вас уже сказал это. Зачем повторять?
Тут вполне тривиальное перечисление знакомых базвордов. Как я уже говорил — опишите мне механизм подобных абстракций поверх uint256, который был бы зерокост. Его нет и именно поэтому вы его не опишите.
Опять же. Как мы уже выясниили — на подобное способен сишный компилятор и мистический хотспот. Остальные, как я понимаю, так же не способны нанять хорошего компиляторщика, да? Вот ведь идиоты.
Шланг всего-то всем миром пилят десятилетие, как и хотспот санраклом. А надо было просто нанять хорошего компиляторщика и он бы за пару лет сваял.
Именно поэтому ни у одного языка не было приличного компилятора до появления ллвм. Они так же — просто не додумались нанять хорошего компиляторщика. Бывает.
Смысл последнего абзаца в том, что есть сообщество. Вы не способны думать в рамках обстоятельств реального мира. Причина — ваша специализация. Так же со всем остальным.
Вся эта тема существует и базируется в рамках жаваскрипт мира. А жаваскрипт стеком не пользуются «хорошие компиляторщиики» и прочее. Можно глянуть на какой-нибудь там тайпскрипт, который не язык, а хелворд. Но это максимум до чего дошел мир жаваскрипта. Хотя есть причина тому, хотя это больше оправдание — он совместим с ЖС.
Regis
superhackkiller1997
Если есть что возразить — я слушаю. Для начала научитесь читать, ведь то, что вы процитировали относится к жаве, а не к житу.
izzholtik
uh.
Вы вообще различаете язык Java, JDK и JRE?
superhackkiller1997
Вот объясните мне, эксперты, что с вами не так? Вы не умеете читать? Хотя что я могу ожидать от верующих — уж явно не объективности.
Специально для вас расшифрую.
Расшифрую ещё более тщательно. Неважно какой там жит. Жит не выпилит ГЦ, жит не выпилит рантайм рефлексию, жит не наделит жаву нормальной типизацией, жит не сделает из дженериков что-то уровня крестовых шаблонов.
izzholtik
Понял, вопросов больше не имею.
Regis
«Java JIT» — встроенный Just In Time компилятор вирутальной машины Java. Преобразует Java-байткод в высокопроизводительный native-код, в том числе выполяет инлайниг, разворачивание циклов, векторизацию и много других оптимизаций. Именно он позволяет Java держать лидирующие позиции в тестах на производительность. И повторюсь, это не что-то отдельное от Java — это часть стандартной JVM.
Так что вы своим комментарием подтвердили, что вы совершенно не компетентны для обсуждения того, что Java может, а что нет.
superhackkiller1997
В мире влажных мечтаний адептов, возможно.
И что же из этого следует?
И что же конкретно я своим комментарием подтвердил и на основании чего.
isvirin Автор
Константы есть. Строчку
можно записать как
Но с точки зрения понимания сути оно мне кажется хуже, хотя и вероятность ошибки ниже.
Есть и другие полезные константы, типа minutes, days, years и т.п.
lany
1 ether
получше, чем восемнадцать нулей, хотя скрывается тот факт, что оно выражено в веях. Возможно, это ничего, если все привыкли считать, что всё и всегда выражено в веях. Но оно опять же явно не так. В коде автора, например, естьtokensPerOneEther = 5000
, а вовсе не цена одного токена в веях. Очевидно, что не всегда удобно работать с веями, надо с разными единицами уметь.superhackkiller1997
Это именно то, о чём я говорил. Вы сознались в том, что не привыкли, а значит привыкли вы к чему? Правильно, к плавучке, где можно менять порядок как угодно и складывать величины разного порядка.
От того вам и не привычен константный порядок.
Явно так.
Правильно, потому что автор так же пока не отвык от плавучки. И именно в веях она и должна быть выражена.
Очевидно, что вам удобно игнорировать те обстоятельства, о которым вас сообщают. Токены — это левая логика. Её не существует в рамках платформы. Это не аналог веям.
На уровне платформы существуют только веи. И никаких других единиц попросту не существует.
TargetSan
Но на входе-выходе существуют эфирные монеты и много чего ещё. У НАСА был спускаемый аппарат, который навернулся из-за нестыковок между метрической и имперской системой. Вы постоянно "тычете" оппонента в "плавучку", которой в его комментариях нет. Он как раз говорит о такой штуке, как единицы измерения, закодированные в виде типов. Это сильно уменьшает вероятность ошибки, т.к. при попытке комбинирования в одной операции вы либо получите ошибку компиляции, либо корректное преобразование, закодированное один раз в логике типа.
superhackkiller1997
Не существует.
Это к теме отношения не имеет. Решение проблемы не в кастылянии, а в приведении всего к одному виду. И именно про это я и говорю.
Нет. Он уже уплыл с темы 10 раз и слушать, что говорят подобные персонажи — себе дороже. Сначала он говорил про зерокост, потом вдруг о зерокосте забыл и прочее и прочее.
Так же, я на этот так же отвечал, но он это проигнорировал. Эфирные монеты и прочее и прочее — это логика сверху языка. Т.е. как в жаве логика работы с рублями. Вы же не требуете от жавы готовой логики работы с рублями? На входе ведь рубли есть.
Плюс, он путает( как и большинство) язык и рантайм. Т.е. язык и набор библиотек, которые поставляются с языком. Я об этом так же говорил. Так же — он не понимает, что токены — это не сущности платформы, а сущности внешние. Я об этом так же сказал.
Никто не говорит о том, что не надо делать удобные обёртки/абстракции. Я говорил о другом, а именно о взаимоисключающих параграфах в предложении данного персонажа. Не надо мне приписывать что-то не моё.
Если там нет слова «плавучка» — это не значит, что её там нет. Если вы не можете это вывести сами, то я вам показал цитату, в которой персонаж явно говорил о том, что логика с константным порядком ему непривычна.
Нельзя преобразовать сантиметры в метры не потеряв сантиметров. Всё это не работает. Вы предлагаете, опять же, экспортировать логику плавучки.
В логике же данной единственные операции, которые имеют смысл — это fromEther и прочее, что итак есть — выше уже показали. Это просто набор констант для смены порядка.
Эти константы позволяют вам приводить любые величины к общему виду — веям. И никаких проблем с преобразование дальше нет. Хотя что-то отличное от веев — вы сами туда тащите.
Ни в чём другом, кроме веев, считать по определению нельзя.
Нельзя считать в других типах. Нельзя в рамках точности милиметры считать в сантиметрах. Это всё плавучка — гоните её, либо сознайтесь в этом.
Всякий раз как вы говорите об эфирах — вы подразумеваете плавучку. Именно плавучка для вас меньшие единицы превращает в доли. Именно поэтому вы так просто рассуждаете о типах-эфирах и прочем( том же преобразовании).
За рамками её просто эфиров быт не может. К эфиру надо прикрутить ещё остаток. И уже у нас не эфир, а пара. И уже во много раз больше операций. И где обещанный зерокост?
Я даже не понимаю как это должно и может работать. WEI.amount(value).toEther() — что мы получаем? Только эфиры? А где веи? А если это веи, то чем они отличаются от веев? Это веи + эфиры? Как это.