Есть несколько широко известных принципов ООП, входящих в аббревиатуру SOLID:
S
ingle responsibility principle (принцип единственной ответственности)O
pen/closed principle (принцип открытости/закрытости)L
iskov substitution principle (принцип подстановки Барбары Лисков)I
nterface segregation principle (принцип разделения интерфейса)D
ependency inversion principle (принцип инверсии зависимостей)
Есть множество приверженцев и противников использования этих принципов, но мы просмотрели множество библиотек на языке Solidity и пришли к выводу, что подход Zeppelin Solutions является наиболее удобным и безопасным. Их библиотека OpenZeppelin.org предоставляет множество небольших смарт-контрактов, которые могут быть скомпилированы для достижения более сложного поведения по паттерну примесей (англ. mixin) через использование множественного наследования в языке Solidity. Вам необходимо произвести декомпозицию обязанностей вашего финального смарт-контракта на множество смарт-контрактов с единственными ответственностями – каждый смарт-контракт должен служить единственной цели. Причем часть необходимых вам контрактов вы скорее всего обнаружите в библиотеках вроде той, что предлагает компания Zeppelin Solutions. Помимо всего прочего вы к тому же сможете протестировать каждый из контрактов по отдельности.
Руководствуясь этими принципами мы разработали смарт-контракты для проведения распродажи токенов: github.com/bitclave/crowdsale. Вы можете заметить в репозитории смарт-контракты
BonusCrowdsale
и TokensCappedCrowdsale
, которые были разработаны таким образом чтобы обрабатывать такие аспекты нашей распродажи, как обработку бонусов участников в зависимости от времени и суммы инвестиций, а также контролировать суммарное число продаваемых токенов. На наш код мы получили довольно хвалебный отзыв аудитора безопасности смарт-контрактов:«Великолепная работа по повторному использованию существующих контрактов библиотек OpenZeppelin! Дополнительные контракты выглядят очень продуманно спроектированными и выглядят хорошим расширением этого фреймвока» («Great work reusing the existing OpenZeppelin libraries! The additional contracts are very thoughtfully designed, and are a good extension of the framework»)?—?Zeppelin Solutions. С полным заключением можно ознакомиться по ссылке.
Для того чтобы успешно применять эти принципы на практике необходимо четко понимать как именно работает множественное наследование. На деле компилятор Solidity преобразует множественное наследование в одиночное наследование. Таки образом после компиляции у каждого смарт-контракта будет всего 1 родитель, обращаться к которому можно через ключевое слово
super
. Возможно следующий пример дополнительно поможет понять как именно работает линеаризация множественного наследования C3:contract A { }
contract B { }
contract C is A, B { } // C(A,B) = ABC
contract D is C, A { } // D(C(A,B),A) = D(ABC,A) = ABCAD !!! Error !!!
contract E is A, C { } // E(A,C(A,B)) = E(A,ABC) = ABCE
Проблема в том, что смарт-контракт
A
не может переопределять C
, потому что C
переопределяет B
, который в свою очередь переопределяет A
:TypeError: Linearization of inheritance graph impossible
contract D is C, A { }
^ — — — — — — — — — — ^
Compiliation failed. See above.
Так же необходимо учитывать, что любое непосредственное наследование контрактов может быть превано в процессе наследования дочерних классов. Обратите внимание, как в следующем примере компилируется контракт
W
, и его родительский контракт Z
становится наследником контракта Y
(который является наследником X
), вместо непосредственного наследования от X
:contract X {}
contract Y is X {} // Y(X) = XY
contract Z is X {} // Z(X) = XZ
contract W is Y, Z {} // W(Y(X),Z(X)) = W(XY, XZ) = XYZW
Возвращаясь к смарт-контракту мультиподписного кошелька Parity Technologies мы ообратили внимание, что он совершенно не декомпозирован. Единственное архитектурное решение замеченное нами: вынос общего кода всех кошельков в единую библиотеку с целью уменьшить стоимость загрузки контракта. Кстати, именно эта особенность и позволила случайному разработчику нарушить работу всех кошельков, сломав единственную библиотеку с общим кодом. Мы поразмышляли на тему множественного владения ресурсом и подготовили своё решение этой задачи в манере библиотек OpenZeppelin. Мы рады представить вам контракт Multiownable.sol, который позволит вам с легкостью добавить функциональность мультиподписи в любые ваши контракты. Его использование так же просто как и использование обычного контракта
Ownable
—?нужно лишь отнаследоваться от него и добавить модификаторы onlyAnyOwner
и onlyManyOwners
к необходимым функциям:contract SimplestMultiWallet is Multiownable {
function () payable { }
function transferTo(address to, uint256 amount) onlyManyOwners {
to.transfer(amount);
}
}
Метод
transferTo
смарт-контракта будет вызван только после того как все владельцы кошелька вызовут его с одинаковыми аргументами. Вы не поверите, но это полный код простейшего мультиподписного кошелька для валюты Эфир. Дополнительно можно реализовать поддержку любых токенов совместимых со спецификацией ERC20, вот такой метод:function transferTokens(address token, address to, uint256 amount) onlyManyOwners {
ERC20(token).transfer(to, amount);
}
Будем рады любому фидбеку сообщества: github.com/bitclave/Multiownable
Комментарии (6)
therealal
11.11.2017 09:39Чисто и лаконично написано.
Но в функцииtransferOwnership
есть цикл, число итераций которого может оказаться порядка тысячи и более:
for (i = 0; i < allPendingOperations.length; i++) {
что приведет к достижению лимита газа блока и невозможности поменять владельцев контракта.
Мультиподпись ethereum wallet также этим страдает (функцияclearPending
).
В своей мультиподписи на базе МП ethereum wallet мы ввели принудительный сброс pending-операций в потенциально опасной ситуации, но я считаю это временным решением — планируем переработать алгоритм сброса в итерационный, который адаптивно учитываетmsg.gas
.k06a Автор
11.11.2017 23:28Спасибо за ваш комментарий, думал насчёт этой проблемы. И видится мне нужно просто ограничить число операций. Можно конечно добавить еще один маппинг для исключения обхода цикла.
k06a Автор
12.11.2017 01:26Что скажете про такой способ ухода от циклов по операциям? https://github.com/bitclave/Multiownable/pull/2
Вынесение всех переменных в структуру не помогло. При пересоздании структуры внутренние маппинги не очищаются! Зато помогли вложенные маппинги, первым ключом которых является фактически номер версии, которая растет при каждой передачи прав владения.
thatsme
Это событие с Parity ещё раз подтверждает, что тьюринг полный язык контрактов — зло. Все кому не лень кинулись на Solidity как в свoё время на JavaScript, независимо от кривизны рук.
Bitcoin script не полнотьюринговый, язык смарт контрактов BitShares не полнотьюринговый. Каждйы для своей области ограничены и детерминистичны. Язык smart-контрактов должен быть детерминистичным, иначе это катастрофа. В этом году из за кривых контрактов уже 6 проблем на всю сеть было. Solidity ЯП на блокчейне, с соответствующими тормозами. Имея тюринг-полный ЯП, всегда можно найти возможность создать неявную проблему в смарт-контракте.
k06a Автор
Но полнота по Тьюрингу слишком привлекательна для проектов. Не всё же кошельки делать и краудсейлы :)
rumkin
Тьюринг полный язык контрактов не зло. Зло — отсутствие инструментов и методик позволяющих проверить получилась ли система детерменизированной.