Солидност Най-добри практики за защита на интелигентен договор

блог 1NewsDevelopersEnterpriseBlockchain ExplainedEvent and ConferencesPressБюлетини

Contents

Абонирайте се за нашия бюлетин.

Имейл адрес

Ние уважаваме вашата поверителност

Развитие на HomeBlogBlockchain

Солидност Най-добри практики за защита на интелигентен договор

От мониторинг до съображения за времеви клей, ето няколко професионални съвета, за да сте сигурни, че вашите Ethereum интелигентни договори са укрепени. от ConsenSys 21 август 2020 г. Публикувано на 21 август 2020 г.

солидност най-добри практики герой

От ConsenSys Diligence, нашият екип от експерти по сигурността на блокчейн.

Ако сте приели присърце мисленето за интелигентен договор и се справяте с идиосинкразиите на EVM, време е да разгледате някои модели на сигурност, специфични за езика за програмиране Solidity. В този обзор ще се съсредоточим върху препоръките за сигурно развитие на Solidity, които също могат да бъдат поучителни за разработване на интелигентни договори на други езици. 

Добре, нека да скочим.

Използвайте assert (), изисквайте (), връщайте () правилно

Функциите за удобство твърдя и изискват може да се използва за проверка на условията и изхвърляне на изключение, ако условието не е изпълнено.

The твърдя функцията трябва да се използва само за тестване на вътрешни грешки и за проверка на инварианти.

The изискват функцията трябва да се използва за осигуряване на валидни условия, като входни данни или променливи на състоянието на договора, или за валидиране на възвръщаеми стойности от обаждания към външни договори. 

Следването на тази парадигма позволява на инструментите за официален анализ да проверят дали невалидният opcode никога не може да бъде достигнат: което означава, че не са нарушени инварианти в кода и че кодът е официално проверен.

твърдост на прагма ^ 0,5,0; договор Споделител {функция sendHalf (адрес за плащане addr) публично платими връщания (uint салдо) {изисква (msg.value% 2 == 0, "Изисква се дори стойност."); // Require () може да има незадължителен низ от съобщения uint balanceBeforeTransfer = address (this) .balance; (bool успех,) = addr.call.value (msg.value / 2) (""); изискват (успех); // Тъй като се върнахме, ако прехвърлянето се провали, не би трябвало // да имаме начин все пак да имаме половината от парите. отстояване (адрес (този) .balance == balanceBeforeTransfer – msg.value / 2); // използва се за вътрешна проверка на грешки връщащ адрес (this) .balance; }} Език на кода: JavaScript (javascript)


Вижте SWC-110 & SWC-123

Използвайте модификатори само за проверки

Кодът в модификатора обикновено се изпълнява преди тялото на функцията, така че всички промени в състоянието или външни повиквания ще нарушат Проверки-ефекти-взаимодействия модел. Освен това тези изявления също могат да останат незабелязани от разработчика, тъй като кодът за модификатора може да е далеч от декларацията за функция. Например външно повикване в модификатор може да доведе до повторна атака:

договор Регистър {собственик на адрес; функция isVoter (адрес _addr) външни връщания (bool) {// код}} Избор на договор {Регистър на системния регистър; модификатор isEligible (адрес _addr) {изискване (registry.isVoter (_addr)); _; } функция voice () isEligible (msg.sender) public {// Code}} Език на кода: JavaScript (javascript)

В този случай договорът на системния регистър може да извърши атака за повторно влизане, като извика Election.vote () вътре в isVoter ().

Забележка: Използвайте модификатори за да замените дублиращи проверки на състоянието в множество функции, като isOwner (), в противен случай използвайте изискване или връщане във функцията. Това прави кода на вашия интелигентен договор по-четлив и по-лесен за одит.

Пазете се закръгляване с целочислено разделение

Цялото цяло деление се закръглява до най-близкото цяло число. Ако имате нужда от по-голяма точност, помислете дали да не използвате множител или да съхраните както числителя, така и знаменателя.

(В бъдеще Solidity ще има фиксирана точка тип, което ще улесни това.)

// лош uint x = 5/2; // Резултатът е 2, всички целочислени подразделения закръгляват НАДОЛУ до най-близкия език integerCode: JavaScript (javascript)

Използването на множител предотвратява закръгляването надолу, този множител трябва да бъде отчетен при работа с x в бъдеще:

// добър множител на uint = 10; uint x = (5 * множител) / 2; Език на кода: JavaScript (javascript)

Съхраняването на числителя и знаменателя означава, че можете да изчислите резултата от числителя / знаменателя извън веригата:

// добър числител на uint = 5; uint знаменател = 2; Език на кода: JavaScript (javascript)

Имайте предвид компромисите между абстрактни договори и интерфейси

И двата интерфейса и абстрактните договори осигуряват един с възможност за персонализиране и повторно използване за интелигентни договори. Интерфейсите, въведени в Solidity 0.4.11, са подобни на абстрактните договори, но не могат да имат внедрени функции. Интерфейсите също имат ограничения като невъзможност за достъп до хранилище или наследяване от други интерфейси, което обикновено прави абстрактните договори по-практични. Въпреки това, интерфейсите със сигурност са полезни за проектиране на договори преди изпълнението. Освен това е важно да се има предвид, че ако договорът наследява абстрактния договор, той трябва да изпълнява всички неприложени функции чрез заместване, или той също ще бъде абстрактен.

Резервни функции

Поддържайте резервните функции прости

Резервни функции се извикват, когато на договор е изпратено съобщение без аргументи (или когато никоя функция не съвпада) и има достъп до 2300 газ, когато е извикан от .send () или .transfer (). Ако искате да можете да получавате етер от .send () или .transfer (), най-много, което можете да направите в резервна функция, е да регистрирате събитие. Използвайте подходяща функция, ако е необходимо изчисляване на повече газ.

// лоша функция () платима {салда [msg.sender] + = msg.value; } // депозит с добра функция (), платим външни {салда [msg.sender] + = msg.value; } функция () платимо {изисквам (msg.data.length == 0); излъчва LogDepositReceived (msg.sender); } Език на кода: JavaScript (javascript)

Проверете дължината на данните във резервни функции

Тъй като резервни функции се извиква не само за обикновени етерни трансфери (без данни), но също така и когато никоя друга функция не съвпада, трябва да проверите дали данните са празни, ако резервната функция е предназначена да се използва само с цел регистриране на получения етер. В противен случай повикващите няма да забележат, ако договорът ви се използва неправилно и се извикват функции, които не съществуват.

// лоша функция () платимо {emit LogDepositReceived (msg.sender); } // добра функция (), платима {изисква (msg.data.length == 0); излъчва LogDepositReceived (msg.sender); } Език на кода: JavaScript (javascript)

Изрично маркирайте платените функции и променливите на състоянието

Започвайки от Solidity 0.4.0, всяка функция, която приема етер, трябва да използва платежен модификатор, в противен случай ако транзакцията има msg.value > 0 ще се върне (освен когато е принуден).

Забележка: Нещо, което може да не е очевидно: Модификаторът за плащане се прилага само за обаждания от външни договори. Ако извикам неплатима функция във платежната функция в същия договор, неплатимата функция няма да се провали, въпреки че msg.value все още е зададена.

Изрично маркирайте видимостта във функциите и променливите на състоянието

Изрично маркирайте видимостта на функциите и променливите на състоянието. Функциите могат да бъдат определени като външни, публични, вътрешни или частни. Моля, разберете разликите между тях, например външните може да са достатъчни вместо публични. За променливи на състоянието външното не е възможно. Изричното обозначаване на видимостта ще улесни улавянето на неправилни предположения за това кой може да извика функцията или да осъществи достъп до променливата.

  • Външните функции са част от интерфейса на договора. Външна функция f не може да бъде извикана вътрешно (т.е. f () не работи, но this.f () работи). Външните функции понякога са по-ефективни, когато получават големи масиви от данни.
  • Публичните функции са част от интерфейса на договора и могат да бъдат извикани вътрешно или чрез съобщения. За публични променливи на състоянието се генерира функция за автоматично получаване (вижте по-долу).
  • Вътрешните функции и променливите на състоянието могат да бъдат достъпни само вътрешно, без да се използва това.
  • Частните функции и променливите на състоянието са видими само за договора, в който са дефинирани, а не в производни договори. Забележка: Всичко, което е в рамките на договор, е видимо за всички наблюдатели, външни за блокчейна, дори частни променливи.

// лош uint x; // по подразбиране е вътрешно за променливи на състоянието, но трябва да се направи изрична функция buy () {// по подразбиране е public // публичен код} // good uint private y; function buy () external {// извиква се само външно или се използва this.buy ()} function utility () public {// извиква се външно, както и вътрешно: промяната на този код изисква да се помисли и за двата случая. } функция internalAction () вътрешен {// вътрешен код} Език на кода: PHP (php)

Вижте SWC-100 и SWC-108

Заключете прагмите към конкретна версия на компилатора

Договорите трябва да бъдат разположени със същата версия на компилатора и флагове, с които са тествани най-много. Заключването на прагмата помага да се гарантира, че договорите няма да бъдат внедрени случайно, използвайки например най-новия компилатор, който може да има по-висок риск от неоткрити грешки. Договорите могат да бъдат разположени и от други, а прагмата показва версията на компилатора, предназначена от оригиналните автори.

// лоша солидност на прагма ^ 0.4.4; // добра прагма солидност 0.4.4; Език на кода: JavaScript (javascript)

Забележка: версия с плаваща прагма (т.е. ^ 0.4.25) ще се компилира добре с 0.4.26-nightly.2018.9.25, но нощните компилации никога не трябва да се използват за компилиране на код за производство.

Внимание: Изявленията на Pragma могат да бъдат пуснати, когато договорът е предназначен за консумация от други разработчици, както в случая с договори в библиотека или EthPM пакет. В противен случай разработчикът ще трябва да актуализира ръчно прагмата, за да компилира локално.

Вижте SWC-103

Използвайте събития за наблюдение на договорната дейност

Може да е полезно да имате начин за наблюдение на дейността на договора след неговото внедряване. Един от начините да се постигне това е да се разгледат всички транзакции по договора, но това може да е недостатъчно, тъй като обажданията за съобщения между договори не се записват в блокчейна. Освен това той показва само входните параметри, а не реалните промени, които се правят в състоянието. Също така събитията могат да се използват за задействане на функции в потребителския интерфейс.

договор Благотворителност {картографиране (адрес => uint) баланси; функция donate () платими публични {салда [msg.sender] + = msg.value; }} договорна игра {функция buyCoins (), платима публично {// 5% отива за благотворителност charity.donate.value (msg.value / 20) (); }} Език на кода: JavaScript (javascript)

Тук договорът за игра ще направи вътрешен разговор с Charity.donate (). Тази транзакция няма да се появи във външния списък на транзакции на благотворителност, но ще се види само във вътрешните транзакции.

Събитието е удобен начин да регистрирате нещо, което се е случило в договора. Събитията, които са били излъчени, остават в блокчейна заедно с другите данни за договора и са на разположение за бъдещ одит. Ето подобрение в горния пример, като се използват събития, за да се предостави история на даренията на благотворителната организация.

договор за благотворителност {// дефиниране на събитие LogDonate (uint _amount); картографиране (адрес => uint) баланси; функция donate () платими публични {салда [msg.sender] + = msg.value; // излъчване на събитие emit LogDonate (msg.value); }} договорна игра {функция buyCoins (), платима публично {// 5% отива за благотворителност charity.donate.value (msg.value / 20) (); }} Език на кода: JavaScript (javascript)

Тук всички транзакции, преминали през договора за благотворителност, директно или не, ще се покажат в списъка на събитията на този договор, заедно със сумата на дарените пари.

Забележка: Предпочитайте по-новите Solidity конструкции. Предпочитайте конструкции / псевдоними като самоунищожение (над самоубийство) и keccak256 (над sha3). Модели като require (msg.sender.send (1 етер)) също могат да бъдат опростени до използване на transfer (), както в msg.sender.transfer (1 етер). Разгледайте Дневник за промяна на солидността за още подобни промени.

Имайте предвид, че „Вградените“ могат да бъдат скрити

В момента е възможно да сянка вградени глобали в Solidity. Това позволява на договорите да заменят функционалността на вградените модули като msg и revert (). Въпреки че това е предназначен, може да заблуди потребителите на договор относно истинското поведение на договора.

договор PretendingToRevert {функция revert () вътрешна константа {}} договор ExampleContract е PretendingToRevert {функция somethingBad () public {revert (); }}

Потребителите на договор (и одиторите) трябва да са наясно с пълния изходен код на интелигентния договор на всяко приложение, което възнамеряват да използват.

Избягвайте да използвате tx.origin

Никога не използвайте tx.origin за оторизация, друг договор може да има метод, който да извика вашия договор (където потребителят има някои средства например) и вашият договор ще упълномощи тази транзакция, тъй като адресът ви е в tx.origin.

договор MyContract {собственик на адрес; функция MyContract () public {owner = msg.sender; } функция sendTo (получател на адрес, uint сума) public {require (tx.origin == owner); (bool успех,) = получател.call.value (сума) (""); изискват (успех); }} договор AttackingContract {MyContract myContract; адрес атакуващ; функция AttackingContract (адрес myContractAddress) public {myContract = MyContract (myContractAddress); нападател = msg.sender; } функция () public {myContract.sendTo (нападател, msg.sender.balance); }} Език на кода: JavaScript (javascript)

Трябва да използвате msg.sender за упълномощаване (ако друг договор извика вашия договор, msg.sender ще бъде адресът на договора, а не адресът на потребителя, който е извикал договора).

Можете да прочетете повече за това тук: Документи за солидност

Внимание: Освен проблема с оторизацията, има шанс tx.origin да бъде премахнат от протокола Ethereum в бъдеще, така че кодът, който използва tx.origin, няма да бъде съвместим с бъдещи версии Виталик: „НЕ приемайте, че tx.origin ще продължи да бъде използваем или значим.“

Също така си струва да се спомене, че с помощта на tx.origin ограничавате оперативната съвместимост между договорите, тъй като договорът, който използва tx.origin, не може да се използва от друг договор, тъй като договорът не може да бъде tx.origin.

Вижте SWC-115

Зависимост от времеви печат

Има три основни съображения при използване на клеймо за изпълнение на критична функция в договор, особено когато действията включват превод на средства.

Манипулация с времеви клеймо

Имайте предвид, че клеймото за време на блока може да бъде манипулирано от майнер. Помислете за това договор:

uint256 постоянна частна сол = block.timestamp; функция произволни (uint Max) постоянни частни връщания (uint256 резултат) {// получаваме най-доброто начално съдържание за произволност uint256 x = сол * 100 / Max; uint256 y = сол * block.number / (сол% 5); uint256 seed = block.number / 3 + (сол% 300) + Last_Payout + y; uint256 h = uint256 (block.blockhash (семе)); връщане uint256 ((h / x))% Max + 1; // произволно число между 1 и Max} Език на кода: PHP (php)

Когато договорът използва клеймото за време за засяване на произволно число, майньорът всъщност може да публикува клеймо за време в рамките на 15 секунди след валидирането на блока, което ефективно позволява на майнера да изчисли предварително опция, по-благоприятна за шансовете им в лотарията. Клеймовете за време не са произволни и не трябва да се използват в този контекст.

Правилото от 15 секунди

The Жълта хартия (Референтната спецификация на Ethereum) не посочва ограничение за това колко блокове могат да се отклоняват във времето, но то уточнява че всеки клеймо трябва да е по-голямо от клеймото на своя родител. Популярни реализации на протокол Ethereum Гет и Паритет и двата отхвърлят блокове с времеви клей за повече от 15 секунди в бъдеще. Следователно, едно добро правило при оценката на използването на клеймото за време е: ако мащабът на вашето събитие, зависещо от времето, може да варира с 15 секунди и поддържа целостта, безопасно е да използвате блок.

Избягвайте да използвате block.number като клеймо за време

Възможно е да се изчисли делта във времето, като се използва свойството block.number и средно време за блокиране, това обаче не е бъдещо доказателство, тъй като времената за блокиране могат да се променят (като реорганизации на вилици и бомба с трудност). При разпродажба, обхващаща дни, правилото за 15 секунди позволява да се постигне по-надеждна оценка на времето.

Вижте SWC-116

Внимание за множествено наследяване

Когато използвате множествено наследяване в Solidity, е важно да разберете как компилаторът съставя графата за наследяване.

окончателен договор {uint public a; функция Final (uint f) public {a = f; }} договор Б е окончателен {int публична такса; функция B (uint f) Final (f) public {} функция setFee () public {такса = 3; }} договор C е окончателен {int публична такса; функция C (uint f) Final (f) public {} функция setFee () public {такса = 5; }} договор A е B, C {функция A () public B (3) C (5) {setFee (); }} Език на кода: PHP (php)

Когато е разположен договор, компилаторът ще линеаризира наследството отдясно наляво (след като ключовата дума е родителите са изброени от най-базовия до най-извлечения). Ето линеаризацията на договор А:

Финал <- Б. <- ° С <- A

Последицата от линеаризацията ще доведе до стойност на таксата 5, тъй като C е най-извлеченият договор. Това може да изглежда очевидно, но представете си сценарии, при които C е в състояние да засенчи ключови функции, да пренареди булеви клаузи и да накара разработчика да напише договори за експлоатация. Понастоящем статичният анализ не повдига проблем със засенчени функции, така че трябва да бъде проверен ръчно.

За да допринесе, Solidity’s Github има проект с всички въпроси, свързани с наследството.

Вижте SWC-125

Използвайте тип интерфейс вместо адреса за безопасност на типа

Когато функция приема адрес на договор като аргумент, по-добре е да предаде интерфейс или тип договор, а не суров адрес. Ако функцията се извика другаде в рамките на изходния код, компилаторът ще предостави допълнителни гаранции за безопасност на типа.

Тук виждаме две алтернативи:

договор за валидация {функция валидира (uint) външни връщания (bool); } договор TypeSafeAuction {// добра функция validateBet (Validator _validator, uint _value) вътрешни връщания (bool) {bool valid = _validator.validate (_value); връщане валидно; }} договор TypeUnsafeAuction {// лоша функция validateBet (адрес _addr, uint _value) вътрешни връщания (bool) {Validator validator = Validator (_addr); bool валиден = валидатор.validate (_value); връщане валидно; }} Език на кода: JavaScript (javascript)

Ползите от използването на договора TypeSafeAuction по-горе могат да се видят от следващия пример. Ако validateBet () бъде извикан с адресен аргумент или тип договор, различен от Validator, компилаторът ще изведе тази грешка:

договор NonValidator {} договор Търг е TypeSafeAuction {NonValidator nonValidator; залог на функция (uint _value) {bool валиден = validateBet (nonValidator, _value); // TypeError: Невалиден тип за аргумент при извикване на функция. // Невалидно имплицитно преобразуване от договор NonValidator // към договор Заявен валидатор. }} Език на кода: JavaScript (javascript)

Избягвайте да използвате extcodesize, за да проверявате за външно притежавани акаунти

Следният модификатор (или подобна проверка) често се използва за проверка дали е било осъществено обаждане от външен акаунт (EOA) или договор по сметка:

// лош модификатор isNotContract (адрес _a) {размер на uint; монтаж {размер: = extcodesize (_a)} изисква (размер == 0); _; } Език на кода: JavaScript (javascript)

Идеята е директна: ако адресът съдържа код, това не е EOA, а договорна сметка. въпреки това, договорът не разполага с изходен код по време на строителството. Това означава, че докато конструкторът работи, той може да осъществява повиквания към други договори, но extcodesize за адреса си връща нула. По-долу е даден минимален пример, който показва как тази проверка може да бъде заобиколена:

договор OnlyForEOA {uint публично знаме; // лош модификатор isNotContract (адрес _a) {uint len; монтаж {len: = extcodesize (_a)} изисква (len == 0); _; } функция setFlag (uint i) public isNotContract (msg.sender) {flag = i; }} договор FakeEOA {конструктор (адрес _a) public {OnlyForEOA c = OnlyForEOA (_a); c.setFlag (1); }} Език на кода: JavaScript (javascript)

Тъй като адресите на договора могат да бъдат предварително изчислени, тази проверка може да се провали и ако провери адрес, който е празен в блок n, но който има договор, разположен в него в някакъв блок, по-голям от n.

Внимание: Този проблем е нюансиран. Ако целта ви е да попречите на други договори да могат да извикат договора ви, проверката extcodeize вероятно е достатъчна. Алтернативен подход е да се провери стойността на (tx.origin == msg.sender), макар и това също има недостатъци.

Възможно е да има и други ситуации, в които проверката extcodeize служи на вашата цел. Описването на всички тях тук е извън обхвата. Разберете основното поведение на EVM и използвайте преценката си.

Сигурен ли е вашият блокчейн код?

Резервирайте еднодневна проверка на място при нашите експерти по сигурността. Резервирайте днес вашето усърдиеSecuritySmart ContractsSolidityNewsletter Абонирайте се за нашия бюлетин за най-новите новини на Ethereum, корпоративни решения, ресурси за разработчици и др. Имейл адресИзключително съдържаниеКак да изградим успешен блокчейн продуктУебинар

Как да изградим успешен блокчейн продукт

Как да настроите и стартирате Ethereum NodeУебинар

Как да настроите и стартирате Ethereum Node

Как да създадете свой собствен API за EthereumУебинар

Как да създадете свой собствен API за Ethereum

Как да създадете социален токенУебинар

Как да създадете социален токен

Използване на инструменти за сигурност при разработването на интелигентен договорУебинар

Използване на инструменти за сигурност при разработването на интелигентен договор

Бъдещето на финансите Digital Assets и DeFiУебинар

Бъдещето на финансите: цифрови активи и DeFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me
Like this post? Please share to your friends:
Adblock
detector
map