13 наиболее распространенных уязвимостей при разработке смарт-контрактов
13 наиболее распространенных уязвимостей при разработке смарт-контрактов

В процессе разработки смарт-контрактов действительно существует множество типов уязвимостей, которые могут привести к потере средств, сбою функций контракта или злонамеренной эксплуатации. Ниже приведены распространенные типы уязвимостей при разработке смарт-контрактов:

1. Реентерабельная атака:

Злоумышленник пользуется разблокированным состоянием контракта во время исполнения и неоднократно выводит средства или ресурсы, рекурсивно вызывая функции в контракте.

Пример

Мы будем использовать Solidity, язык смарт-контрактов Ethereum, чтобы создать простой контракт на пожертвование, а затем продемонстрировать потенциальную повторную атаку на контракт. Сначала мы создаем контракт, который принимает пожертвования, имеет баланс и позволяет пользователям выводить деньги. Код этого контракта может выглядеть так

Язык кода:javascript
копировать
pragma solidity ^0.8.0;

contract VulnerableDonation {
    mapping (address => uint) public balances;
    address payable public owner;

    constructor() {
        owner = payable(msg.sender);
    }

    function donate() public payable {
        // получать пожертвования
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint _amount) public {
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        // Баланс следует сначала уменьшить, а затем перенести, но порядок обратный.
        msg.sender.transfer(_amount);
        balances[msg.sender] -= _amount;
    }
}

Обратите внимание, проблема здесь в том, что в функции вывода мы сначала пытаемся перевести деньги пользователю и только потом уменьшаем его баланс. Это небезопасно, поскольку операция перевода активирует функцию приема или возврата получателя, что дает злоумышленнику возможность вызвать функцию вывода для повторного вывода денег.

Теперь давайте создадим контракт злоумышленника, который сможет использовать эту уязвимость:

Язык кода:javascript
копировать
pragma solidity ^0.8.0;

contract Attacker {
    VulnerableDonation donationContract;

    constructor(address _donationAddress) {
        donationContract = VulnerableDonation(_donationAddress);
    }

    fallback() external payable {
        if (address(this).balance > 0) {
            // рекурсивный вызов withdraw Функция, продолжать выводить деньги, пока есть баланс
            donationContract.withdraw(address(this).balance);
        }
    }

    function attack() public payable {
        // первый звонок donate Функция Внесение средств в договор дарения
        donationContract.donate{value: msg.value}();
        // и сразу позвони withdraw Функция Начать реентерабельную атаку
        donationContract.withdraw(address(this).balance);
    }
}

В контракте злоумышленника автоматически срабатывает резервная функция при поступлении средств. Если в контракте еще есть баланс, он будет рекурсивно вызывать функцию вывода контракта пожертвования, пытаясь вывести как можно больше, пока средств не останется. осталось перенести пока. Чтобы обеспечить безопасность контракта, правильным подходом является уменьшение баланса пользователя перед переводом, чего можно добиться, просто изменив порядок функции вывода:

Язык кода:javascript
копировать
function withdraw(uint _amount) public {
    require(balances[msg.sender] >= _amount, "Insufficient balance");
    balances[msg.sender] -= _amount;
    // Передачу следует выполнять после обновления переменных состояния.
    msg.sender.transfer(_amount);
}

так,Даже если злоумышленник попытается позвонить еще раз, прежде чем перевести деньгиwithdraw функция, они также обнаружат, что их баланс был обновлен, и они не смогут снова снять деньги.

2. Целочисленное переполнение и опустошение:

Когда результат математической операции выходит за пределы диапазона, который может представлять целочисленный тип, это приводит к неправильному переносу значения, что может быть использовано злоумышленником для получения дополнительных токенов или ресурсов.

переполнение

Предположим, у нас есть смарт-контракт,Он получает депозиты пользователей и сохраняет их в переменной. Если сумма, которую пользователь пытается внести, плюс существующий баланс превышает максимальное значение целого числа (в Solidity,uint256Максимальное значение типа2^256-1),Произойдёт переполнение.

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract OverflowExample {
    uint256 public balance;

    function deposit(uint256 amount) public {
        balance += amount;
    }

    function getBalance() public view returns (uint256) {
        return balance;
    }
}
тестовое переполнение

Для тестового переполнение,Мы предполагаемbalanceУжеuint256Максимальное значение типа,Попробуйте еще раз сохранить любое положительное число.,вызовет переполнение,То есть результат будет меняться от максимального значения до 0.

Язык кода:javascript
копировать
// Предположим, что баланс уже равен максимальному значению uint256.
uint256 maxUint256 = type(uint256).max;
balance = maxUint256;
// Попытка сохранить любое положительное число приведет к переполнению.
deposit(1);
// В это время баланс станет 0
перелив Пример

Недополнение обычно происходит при операциях вычитания. Если вы вычитаете большее число из меньшего числа, результат будет меньше минимального целочисленного значения (для беззнаковых целых чисел минимальное значение равно 0), что приводит к переполнению.

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UnderflowExample {
    uint256 public balance;

    function withdraw(uint256 amount) public {
        balance -= amount;
    }

    function getBalance() public view returns (uint256) {
        return balance;
    }
}
тестовое опустошение

В целых числах без знака отсутствие переполнения фактически приводит к переходу значения от 0 до максимального значения, но обычно это не ожидаемое поведение и по-прежнему считается ошибкой.

Язык кода:javascript
копировать
// Предположим, баланс равен 0
balance = 0;
// Попытка вынуть любое положительное число приведет к переполнению
withdraw(1);
// В это время баланс станет максимальным значением uint256.
решение

Чтобы избежать целочисленного переполнения и опустошения,Solidityпредоставил Безопасностьматематическая библиотекаSafeMath,Он содержит целочисленную арифметику, которая проверяет наличие переполнения и потери значения. Начиная с Solidity 0.8.0,Безопасностьматематические операторыcheckedAdd, checkedSub, checkedMul, и checkedDivбыть представленным,Исключения могут быть автоматически обнаружены и выброшены.

Язык кода:javascript
копировать
using SafeMath for uint256;

function deposit(uint256 amount) public {
    balance = balance.checkedAdd(amount);
}

function withdraw(uint256 amount) public {
    balance = balance.checkedSub(amount);
}

Таким образом, если обнаружено переполнение или опустошение, Solidity автоматически выдаст исключение, предотвращая выполнение транзакции и тем самым защищая контракт от таких ошибок.

3. Несанкционированный доступ:

Если смарт-контракт имеет недостаточный контроль доступа к критическим функциям, злоумышленник может выполнить операции, которые не следует разрешать, например изменение состояния контракта или вывод средств.

Несанкционированный доступ к Примеру

Предположим, у нас есть смарт-контракт,Используется для управления депозитами и снятием средств пользователей. В этом примере,Контракт не ограничивает должным образом тех, кто может звонитьwithdrawфункция。

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleBank {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    // Отсутствие контроля доступа, эту функцию может вызвать любой желающий
    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        payable(msg.sender).transfer(amount);
        balances[msg.sender] -= amount;
    }
}

в этом контракте,withdrawфункция Можно позвонить напрямую с любого адреса,Пока адрес имеет достаточный баланс. но,Если в договоре есть логические ошибки или состояние хаотично,Это может привести к незаконному выводу средств.

поведение злоумышленника

Злоумышленник может позвонитьwithdrawфункция,Даже если им не хватает баланса,Также возможен успешный вывод средств из-за ошибок в определенных состояниях контракта. например,Если где-то в контракте ошибочно увеличивается баланс атакующего,Злоумышленники могут воспользоваться этим для вывода средств, которые им не принадлежат.

решение

Для решения проблемы несанкционированного доступа,Нам нужно добавить модификатор доступа перед функцией.,Убедитесь, что есть только определенные ролиили Адрес можно назватьwithdrawфункция。Здесь мы используем простойonlyOwnerМодификатор для ограничения звонков владельцу контракта。

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SecureBank {
    address private owner;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only the contract owner can call this function");
        _;
    }

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    // Ограничьте вызовы владельцам с помощью модификатора onlyOwner.
    function withdraw(uint256 amount) public onlyOwner {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        payable(msg.sender).transfer(amount);
        balances[msg.sender] -= amount;
    }
}

Сейчас,Только создатель контракта(Прямо сейчасowner)Можно позвонитьwithdrawфункция。Это предотвращает непосредственный вывод средств неавторизованными пользователями.,Улучшена производительность контракта. Уведомление,Этого простого механизма контроля доступа может быть недостаточно для решения сложных сценариев.,Вам может потребоваться более сложная система ролей и разрешений.,Например, используйтеOpenZeppelinизOwnableиAccessControlБиблиотекачтобы обеспечить более детальноеизконтроль доступа。

4. Неправильный порядок наследования:

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

Неправильный порядок наследования Пример

Предположим, у нас есть два контракта ParentA и ParentB.,И дочерний контракт Child унаследовал от этих двух контрактов. Контракт ParentA содержит конструктор Functionи функцию setOwner.,И ParentB также определяет функцию setOwner.,Но функции у него разные。насиз Цель состоит в том, чтобы сделатьChildКонтракт может Вызов функции setOwner объекта ParentA,но Неправильный порядок наследованияприведет к вызовуиздаParentBиз Версия

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ParentA {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function setOwner(address newOwner) public {
        owner = newOwner;
    }
}

contract ParentB {
    function setOwner(address newOwner) public {
        // Реализация здесь отличается от ParentA, но нас не интересуют конкретные детали.
    }
}

// Неправильный порядок наследования
contract Child is ParentB, ParentA {
    // ...
}

В приведенном выше коде,Дочерний контракт наследует ParentB и ParentA. Однако,В солидности,Если два родительских контракта определяют одно и то же имя. Функция,Порядок наследования определяет, какая функция будет переопределена первой. поэтому,В Детском контракте,setOwnerфункция на самом деле является версией ParentB.,Вместо той версии ParentA, которую мы ожидали.

решение

Чтобы решить эту проблему, нам нужно настроить порядок наследования, чтобы гарантировать, что дочерний контракт может вызывать правильную функцию setOwner. В то же время, чтобы четко указать, какую родительскую функцию контракта мы хотим вызвать, мы можем использовать ключевое слово super, предоставляемое Solidity.

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ParentA {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function setOwnerA(address newOwner) public {
        owner = newOwner;
    }
}

contract ParentB {
    function setOwnerB(address newOwner) public {
        // Реализация здесь отличается от ParentA.
    }
}

// Правильный порядок наследования
contract Child is ParentA, ParentB {
    // Вызов функции setOwner объекта ParentA
    function setOwner(address newOwner) public {
        ParentA.setOwnerA(newOwner); // Явно вызовите setOwnerA для ParentA.
    }
}

В этой модифицированной версии,Дочерний контракт сначала наследуется от ParentA.,Это означает, что переменная состояния функций ParentA будет инициализирована раньше, чем переменная состояния ParentB. также,Мы переименовали функцию setOwner в ParentA иParentB, чтобы избежать конфликтов имен.,и В Детском контрактеопределяет новыйизsetOwnerфункция,Он явно вызывает setOwnerAфункция в ParentA.

Таким образом, мы гарантируем, что функция setOwner в контракте Child вызывает версию ParentA, избегая проблем с покрытием функций, вызванных неправильным порядком наследования.

5. Атака по короткому адресу (Short Address Attack)

Атака по короткому адресуShort Address Attack)существовать Эфириумсерединада指利用Эфириумадресиз Шестнадцатеричный формат(40персонажи,т.е. 20 байт) и какой-то смарт-контракт, неправильная обработка параметров адреса лазейки,Средство атаки для выполнения вредоносных операций. Эта атака в основном происходит, когда смарт-контракт неправильно проверяет длину параметра адреса.,Хотя фактическая длина адреса Ethereum фиксирована,Однако злоумышленник может попытаться передать более короткую адресную строку.,Попытайтесь обманом заставить контракт выполнить неожиданную функцию.

В Solidity переменные типа адрес всегда занимают 20 байт, поэтому передача короткого адреса напрямую не вызовет проблем, поскольку Solidity автоматически заполнит его до 20 байт. Однако некоторые контракты могут получать данные из внешних вызовов, и если эти данные неправильно интерпретируются как адрес, а контракт не обрабатывает и не проверяет эти данные правильно, могут возникнуть атаки на короткие адреса.

Пример,показал Атака по короткому адресуизскрытыйсуществоватьриск:
Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract VulnerableContract {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    // Неправильная попытка синтаксического анализа произвольных данных в адрес
    function setAddress(bytes data) public {
        // Примечание. Здесь используется необычный метод для анализа данных по адресам.
        // Фактически, если длина данных меньше 20 байт, это приведет к неверному адресу.
        assembly {
            owner := mload(add(data, 0x14)) // Загрузите 20 байт данных и назначьте их владельцу.
        }
    }

    function getOwner() public view returns (address) {
        return owner;
    }
}

В этом примере,VulnerableContract имеет публичную функцию setAddress.,Он принимает данные параметра типа байты.,и попробуйте проанализировать его по адресу на ассемблере низкого уровня,Затем установите его как владельца контракта. Если длина данных, переданных злоумышленником, меньше 20 байт,Solidity автоматически заполнит оставшиеся байты 0.,Это может привести к тому, что в качестве владельца будет установлен неверный адрес.

Процесс атаки

Предположим, злоумышленник создает данные длиной менее 20 байт (например.,Содержит только 10 байт полезной нагрузки),ивызовsetAddressфункция。ХотяSolidityавтоматически уменьшит недостаточностьиз Частично заполнен0,Но если контракт не проходит должным образом и не обрабатывает эту ситуацию,Тогда владельцу может быть присвоен неожиданный адрес.,Это может быть неверный адрес или адрес, контролируемый злоумышленником.

защитные меры

Для защиты от атак по коротким адресам разработка смарт-контрактов должна:

  • 1. Проверьте длину данных: убедитесь, что все полученные адресные данные имеют длину 20 байт.
  • 2. Используйте функцию типа Безопасность: избегайте прямого использования операторов ассемблера низкого уровня для обработки данных, а используйте функцию Solidity типа Безопасность.
  • 3. Модульное тестирование. Проведите детальное модульное тестирование.,Включая граничные условия и нештатные ситуации,Убедитесь, что контракт работает правильно при различных входных данных.
  • 4. В актуальном состоянии,Следует избегать прямых манипуляций с адресами в низкоуровневой сборке.,Вместо этого данные адреса обрабатываются с использованием проверки типа «Безопасные функции», предоставляемой Solidity.

6. Утверждение не выполнено:

утверждение(assert)существоватьсмарт-контрактсередина Используется для обеспечения внутренней логикиизпоследовательностьиправильность,Но при неправильном использовании,Это действительно может привести к несчастным случаямиздоговорпрекращениеили Средства заблокированы。Это потому, чтоassertВ основном используется для тестирования внутренних программ.изошибка,Например, ошибки алгоритма и логические ошибки.,Предполагается, что эти ошибки не возникают при нормальной работе. Как только утверждение не удалось,Транзакция будет немедленно отменена,Плата за газ не возвращается.,Это может иметь катастрофические последствия для пользователей контракта.,Особенно, если это приводит к недоступности критического функционала контракта.

Вот пример неправильного использования Assert, которое может привести к блокировке средств:

Неправильное использование утверждения
Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract WithdrawalContract {
    address payable public owner;
    uint256 public balance;

    constructor() {
        owner = payable(msg.sender);
        balance = 0;
    }

    receive() external payable {
        balance += msg.value;
    }

    function withdraw(uint256 amount) public {
        assert(msg.sender == owner); // Гарантирует, что только владелец контракта может вывести средства
        require(balance >= amount, "Insufficient funds"); // Убедитесь, что у вас достаточно баланса
        balance -= amount;
        owner.transfer(amount); // Перевести средства владельцу
    }
}

В этом контракте Assert(msg.sender == Owner) используется, чтобы гарантировать, что только владелец контракта может вызвать функцию вывода. Однако если после развертывания контракта в качестве адреса владельца случайно будет установлен недопустимый адрес (например, адрес без закрытого ключа), утверждение всегда будет завершаться неудачно, и средства будут навсегда заблокированы в контракте, поскольку никто не сможет вызовите функцию вывода средств для вывода средств.

решение

Чтобы избежать риска блокировки средств, вы можете рассмотреть следующие стратегии улучшения:

  • 1、использоватьrequireзаменятьassert:Для пользовательского вводаилипроверка предварительных условий,использоватьrequireболее уместно,Потому что там четко написано, что это проверка внешних условий,Вместо внутренней логической ошибки.
  • 2. Добавьте функцию экстренного вывода: разработайте механизм, позволяющий снимать средства в чрезвычайных ситуациях. Например, если адрес владельца заблокирован, может существовать «доска» с несколькими подписями, позволяющая решить, как разблокировать средства.
  • 3. Обеспечьте смену владельца контракта: разрешите смену владельца контракта в случае, если первоначальный владелец потеряет закрытый ключ или адрес будет заблокирован.
решение Пример:
Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ImprovedWithdrawalContract {
    address payable public owner;
    uint256 public balance;

    constructor() {
        owner = payable(msg.sender);
    }

    receive() external payable {
        balance += msg.value;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only the contract owner can call this function");
        _;
    }

    function withdraw(uint256 amount) public onlyOwner {
        require(balance >= amount, "Insufficient funds");
        balance -= amount;
        owner.transfer(amount);
    }

    // Добавить функцию, позволяющую менять владельца
    function changeOwner(address payable newOwner) public onlyOwner {
        owner = newOwner;
    }
}

В этом улучшенном контракте мы использовали требование для проверки условий и добавили функцию ChangeOwner, которая позволяет текущему владельцу при необходимости изменить адрес владельца, избегая тем самым риска постоянной блокировки средств.

7. Уязвимость контрактного агента

режим проксисуществоватьсмарт-контрактразвиватьсерединаочень распространенный,尤Чтодасуществоватьобновлениеи Модульная конструкциясередина。агентский договор(Proxy Contract) часто используется для отделения логической реализации от внешнего интерфейса контракта, позволяя обновлять или заменять базовую реализацию без изменения интерфейса. Однако если процесс инициализации прокси-контракта не выполняется должным образом, он может стать точкой входа для атак.

Пример: Инициализация агентского контракта лазейки

Предположим, у нас есть следующий шаблон агентского договора.,ЧтосерединаimplementationПеременные указывают на реальную логику выполнения.издоговорадрес:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Proxy {
    address private implementation;

    constructor (address _implementation) {
        implementation = _implementation;
    }

    fallback() external payable {
        address impl = implementation;
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
            assembly {
                let free := mload(0x40) 
                mstore(free, ptr)
                mstore(0x40, add(free, 0x20))
            }
            switch result
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }
}

Этот прокси-контракт принимает адрес контракта реализации через конструктор и сохраняет его в переменной реализации. После этого любые транзакции, отправленные в прокси-контракт, будут перенаправлены в контракт реализации.

Направление атаки

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

решение

Чтобы предотвратить этот тип атаки, нам необходимо убедиться, что процесс инициализации прокси-контракта является безопасным. Вот одно из возможных решений:

  • 1. Используйте шаблон инициализатора: введите состояние инициализации, чтобы гарантировать, что прокси-контракт может быть инициализирован только один раз, а процесс инициализации строго контролируется. Вы можете использовать модификатор инициализатора, чтобы отметить методы, которые следует вызывать только во время инициализации.
  • 2. Внедрить проверку владения: гарантировать, что только владелец контракта или заранее определенный адрес может установить реализацию.

решение Пример:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

abstract contract Initializable {
    bool initialized = false;

    modifier initializer() {
        require(!initialized, "Already initialized");
        initialized = true;
        _;
    }
}

contract SecureProxy is Initializable {
    address private implementation;
    address private admin;

    constructor(address _implementation, address _admin) initializer {
        implementation = _implementation;
        admin = _admin;
    }

    function setImplementation(address _newImplementation) public {
        require(msg.sender == admin, "Only admin can set the implementation");
        implementation = _newImplementation;
    }

    fallback() external payable {
        // ... (same as before)
    }
}

В этой улучшенной версии мы представили абстрактный контракт Initializable для управления состоянием инициализации и применили модификатор инициализатора к конструктору. Кроме того, мы добавили метод setImplementation, который позволяет владельцу контракта (администратору) обновлять адрес реализации, что еще больше повышает безопасность.

8. Уязвимость зависимости от времени

Уязвимости, зависящие от времени, являются распространенной проблемой безопасности в смарт-контрактах, особенно в средах блокчейнов, таких как Ethereum. Это связано с тем, что майнеры могут в определенной степени манипулировать временными метками блоков блокчейна, что делает смарт-контракты, основанные на временных метках, уязвимыми для атак. Злоумышленник может получить несправедливое преимущество или нанести убытки, контролируя временные метки блоков для запуска определенных условий в контракте.

Пример: зависимость от времени в кредитных договорахлазейки.

Предположим, у нас есть кредитный договор с повременной оплатой, по которому заемщик должен погасить кредит в течение определенного периода времени, в противном случае ему грозят высокие штрафные ставки или он теряет залог. Контракт может выглядеть следующим образом:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LoanContract {
    address public borrower;
    uint256 public loanAmount;
    uint256 public deadline;

    constructor(address _borrower, uint256 _loanAmount, uint256 _deadline) {
        borrower = _borrower;
        loanAmount = _loanAmount;
        deadline = block.timestamp + _deadline; // Установить срок погашения
    }

    function repayLoan() public {
        require(msg.sender == borrower, "Only borrower can repay");
        require(block.timestamp <= deadline, "Deadline passed");

        // Логика погашения кредита...
    }

    function claimCollateral() public {
        require(block.timestamp > deadline, "Deadline not yet passed");
        // Логика невозврата кредита и конфискации залога...
    }
}

В этом контракте срок рассчитывается на основе временной метки текущего блока, и заемщик должен погасить кредит до истечения срока. Однако, если злоумышленник контролирует процесс майнинга, он может задержать отправку новых блоков, искусственно продлевая временную метку блока, чтобы создать впечатление, что крайний срок еще не наступил, тем самым предотвращая конфискацию залога или, наоборот, досрочную отправку новых блоков. , в результате чего срок наступает раньше, что вынуждает заемщика платить штрафные проценты.

решение

Чтобы устранить уязвимость, связанную с зависимостью от времени, можно использовать следующие стратегии:

  • 1. Используйте службу Oracle. Внедрите надежную службу Oracle для предоставления незащищенных временных меток, что может снизить влияние майнеров на манипулирование временными метками блоков.
  • 2. Используйте события в цепочке в качестве базы времени. Например, в качестве базы времени можно использовать определенную высоту блока, поскольку майнеры не могут легко манипулировать высотой блока.
  • 3. Добавьте буфер времени. Добавьте определенное время буфера в логику, связанную со временем, чтобы уменьшить зависимость от точных меток времени.
  • 4. Используйте протокол медианного времени (MTP). Подобно протоколу медианного времени в сети Биткойн, медиану нескольких временных меток последних блоков можно использовать для расчета более стабильной точки отсчета времени.

Например, мы можем изменить приведенный выше кредитный договор, чтобы использовать высоту блока в качестве временной базы:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LoanContract {
    address public borrower;
    uint256 public loanAmount;
    uint256 public deadlineBlock;

    constructor(address _borrower, uint256 _loanAmount, uint256 _deadlineBlocks) {
        borrower = _borrower;
        loanAmount = _loanAmount;
        deadlineBlock = block.number + _deadlineBlocks; // Настройка блоков сроков погашения
    }

    function repayLoan() public {
        require(msg.sender == borrower, "Only borrower can repay");
        require(block.number <= deadlineBlock, "Deadline block passed");

        // Логика погашения кредита...
    }

    function claimCollateral() public {
        require(block.number > deadlineBlock, "Deadline block not yet passed");
        // Логика невозврата кредита и конфискации залога...
    }
}

Путем изменения зависимости времени на зависимость высоты блока,Мы уменьшили возможность майнеров манипулировать временными метками.,Это повышает справедливость договора. Однако,Каждое решение имеет свои компромиссы,Например, использование высоты блока может привести к неопределенности, связанной со временем генерации блока.,Поэтому в практическом применении Требует тщательной оценкии Выберите наиболее подходящийизплан。

9. Ограничение газа и DoS-атаки

Лимит газа и DoS (Отказ of Атаки обслуживания (отказ в обслуживании) являются распространенной угрозой в средах блокчейнов, особенно для таких платформ, как Ethereum, где Gas — это единица измерения стоимости выполнения транзакции. Первоначальное намерение Газового механизма – предотвратить бесконечное цикли Злоупотребление ресурсами, но оно также предоставляет злоумышленникам пространство для использования.

Механизм ограничения газа

В Ethereum каждая транзакция несет определенное количество газа. Это делается для того, чтобы любые выполняемые операции не потребляли слишком много вычислительных ресурсов, что позволяет избежать перегрузки сети или истощения ресурсов. Когда транзакция начинает выполняться, она вычитает комиссию из общей суммы газа, предоставленного трейдером, до тех пор, пока исполнение контракта не будет завершено или газ не будет исчерпан. Если во время выполнения Gas будет исчерпан, транзакция будет отменена, и израсходованный Gas не будет возвращен пользователю.

Метод DoS-атаки
Выхлопные газы

Злоумышленник может намеренно потреблять большое количество газа, создавая очень сложные транзакции или смарт-контракты, чтобы обычные транзакции не могли быть включены в блок. Например, злоумышленник может создать контракт, который выполняет большое количество вычислений или операций хранения при получении сообщения, потребляя количество газа, близкое к максимальному пределу газа. Когда в сеть одновременно отправляется множество таких транзакций, они занимают большую часть или даже всю емкость газа, в результате чего обычные транзакции других пользователей не подтверждаются, тем самым достигая эффекта отказа в обслуживании.

бесконечный цикл

Другой способ DoS-атаки — превратить смарт-контракт в бесконечный. цикл,Это приведет к немедленному исчерпанию газа.,Транзакция не удалась и была отменена. Эта атака обычно происходит при наличии ошибки в логике контракта.,Например, условия выхода из цикла обрабатываются неправильно.,илисуществоватьрекурсивный вызовсередина缺少прекращение条件。Когда договор вступит в силубесконечный циклчас,Он попытается израсходовать весь доступный газ.,В конце концов транзакция не удалась,и может сделать контракт недоступным.

защитные меры

Чтобы защититься от такого типа DoS-атак, разработчикам необходимо принять некоторые меры предосторожности при написании смарт-контрактов:

  1. Ограничьте количество циклов:Убедитесь, что все циклы являются явнымиизпрекращение条件,избегатьбесконечный возможность цикла.
  2. Оптимизация эффективности кода:Минимизируйте ненужноеизвычислитьискладские операции,Избегайте алгоритмов высокой сложности.
  3. использовать Безопасностьрамкаи Библиотека:Используйте, например.OpenZeppelinждатьсмарт-контракт Безопасность Библиотека,Обычно они содержат тщательно проверенные шаблоны безопасности.,Может помочь избежать распространенных ошибок безопасности.
  4. проверка кодаитест:Регулярно проводитьпроверка Аудит кодаи Безопасности с использованием формальных инструментов проверки для проверки потенциальных лазейок.
  5. Установить лимит газа:существоватьсмарт-контрактвызовсередина Разумная установкаизGasверхний предел,Не позволяйте злонамеренным вызовам потреблять слишком много ресурсов.
  6. Динамическое ценообразование на газ:Учитывайте динамику реализацииизGasмеханизм ценообразования,Автоматически корректируйте цены на газ в зависимости от нагрузки сети.,поощрять определение приоритетности важных транзакций.

Благодаря этим мерам,Позволяет существенно снизить риск DoS-атак на смарт-контракт,Обеспечьте стабильность сети и активов пользователя. Однако,Из-за сложности среды блокчейна,Постоянное понимание безопасности и новейшие практики безопасности имеют важное значение.

лазейкидоговор Пример
Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract InfiniteLoopVulnerable {
    function loopUntilZero(uint256 startValue) public payable {
        uint256 currentValue = startValue;
        while (currentValue > 0) {
            currentValue--;
        }
        // Нормальная работа...
    }
}

в этом контракте,Функция LoopUntilZero войдет в бесконечный цикл.,Если startValue установлено достаточно большим,Тогда этот контур будет потреблять весь доступный газ.,Вызывает сбой транзакции и ее откат.

Демонстрация атаки

Злоумышленник может вызвать функцию LoopUntilZero, передав очень большое значение, например 2^256-1, что сделает завершение цикла практически невозможным и израсходует весь газ.

Язык кода:javascript
копировать
InfiniteLoopVulnerable contract = new InfiniteLoopVulnerable();
contract.loopUntilZero(2**256-1);
защитные меры

Чтобы предотвратить этот бесконечный DoS-атаки цикла, нам необходимо добавить некоторые ограничения и оптимизации в дизайн контракта:

  • 1、Ограничьте количество циклов: Вы можете установить верхний предел максимального количества циклов, чтобы избежать бесконечного Возникновение цикл.
  • 2. Проверьте и исправьте логику: убедитесь, что в цикле присутствуют правильные условия выхода.
  • 3. Оптимизация эффективности использования газа: максимально сократите операции в каждом цикле, чтобы снизить потребление газа.

Вот отремонтированный контракт. Пример:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SafeInfiniteLoop {
    function safeLoopUntilZero(uint256 startValue) public payable {
        require(startValue <= 10000, "Value too large"); // Установите максимальное количество петель
        uint256 currentValue = startValue;
        while (currentValue > 0) {
            currentValue--;
        }
        // Нормальная работа...
    }
}

10. Неправильное управление разрешениями:

Неправильное управление разрешениями — одна из распространенных проблем безопасности в смарт-контрактах, особенно когда администратору или конкретным учетным записям предоставляются чрезмерные разрешения. Если ключевые функции контракта, такие как передача активов, изменение статуса контракта и обновление логики контракта, могут выполняться по желанию неавторизованными лицами, это создаст серьезный риск. Ниже я приведу пример, показывающий Неправильное управление разрешениями о возможных последствиях и о том, как смягчить этот риск посредством разумного проектирования.

лазейкидоговор Пример

Предположим, у нас есть смарт-контракт,Используется для управления выпуском и передачей цифрового актива. в этом контракте,Учетным записям администраторов предоставляются неограниченные полномочия.,Новые активы могут быть отчеканены и переведены на любой счет без ограничений.

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MismanagedPermissions {
    mapping(address => uint256) public balances;
    address public admin;

    constructor() {
        admin = msg.sender;
    }

    function mint(address to, uint256 amount) public {
        require(msg.sender == admin, "Only admin can mint");
        balances[to] += amount;
    }

    function transfer(address from, address to, uint256 amount) public {
        require(balances[from] >= amount, "Insufficient balance");
        balances[from] -= amount;
        balances[to] += amount;
    }
}

В этом контракте функция mint позволяет учетным записям администраторов создавать новые активы без ограничений. Хотя это может показаться разумным разрешением, но если безопасность учетной записи администратора скомпрометирована или разработчик контракта по ошибке устанавливает ненадежный адрес в качестве администратора, это открывает дверь для злоумышленников.

Демонстрация атаки

Злоумышленник может получить закрытый ключ учетной записи администратора различными способами, либо разработчик контракта может случайно установить вредоносный адрес в качестве администратора. Как только злоумышленник получит контроль над учетной записью администратора, он сможет по своему желанию вызвать функцию mint, создать неограниченное количество активов и перенести их на свою учетную запись, тем самым получая незаконную прибыль.

Язык кода:javascript
копировать
MismanagedPermissions contract = new MismanagedPermissions();
contract.mint(msg.sender, 1000000); // Злоумышленники вычеканили большое количество активов
решение

Чтобы предотвратить проблемы безопасности, вызванные неправильным управлением разрешениями, мы можем принять следующие меры:

  • 1. Принцип минимальных привилегий: предоставляйте только минимальные привилегии, необходимые для выполнения определенных задач. Например, администратору можно предоставить - 2. Разрешение на чеканку активов, но это разрешение должно быть ограничено, например, в день можно чеканить только определенное количество активов.
  • 3. Многофакторная аутентификация. Благодаря внедрению механизма мультиподписи или многофакторной аутентификации, даже если учетная запись администратора скомпрометирована, для выполнения ключевых операций требуется несколько независимых утверждений.
  • 4. Журнал аудита разрешений: записывает все использование разрешений для упрощения мониторинга и аудита.
  • 5. Своевременность разрешения: Установите срок действия разрешения. По истечении срока требуется повторная авторизация.

Ниже приведен улучшенный пример контракта, который добавляет ограничения разрешений и механизм мультиподписи:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SafePermissions {
    mapping(address => uint256) public balances;
    mapping(address => bool) public admins;
    uint256 public dailyMintLimit;
    uint256 public dailyMinted;

    constructor(uint256 _dailyMintLimit) {
        dailyMintLimit = _dailyMintLimit;
        admins[msg.sender] = true; // Начальный администратор
    }

    modifier onlyAdmin() {
        require(admins[msg.sender], "Only admin can perform this action");
        _;
    }

    function mint(address to, uint256 amount) public onlyAdmin {
        require(dailyMinted + amount <= dailyMintLimit, "Daily mint limit exceeded");
        balances[to] += amount;
        dailyMinted += amount;
    }

    function addAdmin(address newAdmin) public onlyAdmin {
        admins[newAdmin] = true;
    }

    function removeAdmin(address adminToRemove) public onlyAdmin {
        delete admins[adminToRemove];
    }
}

В этом улучшенном контракте,Мы ввели концепцию нескольких администраторов,и установите дневной лимит на отчеканенные активы,Чтобы предотвратить неограниченное создание активов. в то же время,Мы также предоставляем возможность добавлять и удалять администраторов.,Для этого требуются существующие права администратора.

благодаря этим улучшениям,Мы можем значительно повысить безопасность контракта.,Уменьшите риск Неправильного управления разрешениями. в практическом применении,Его также необходимо сочетать с конкретными бизнес-сценариями и потребностями в безопасности.,Дальнейшая доработка механизма управления разрешениями и безопасности.

11. Внешний вызов:

существоватьсмарт-контрактразвиватьсередина,Вызов ненадежных внешних контрактов является распространенной точкой риска. Это потому, что,Когда вы вызываете функцию другого контракта,Вы фактически выполняете код этого контракта.,И это может привести к поведению, которого вы не ожидали,В том числе злонамеренное поведение. Ниже я проиллюстрирую этот риск с помощью примера.,и предложить соответствующие стратегии смягчения последствий.

лазейкидоговор Пример

Предположим, у нас есть смарт-контракт, который позволяет пользователям выполнять определенную задачу, например погашение токенов, путем вызова внешнего контракта. Здесь мы предполагаем, что внешний контракт предоставляет функцию TransferFrom для перевода токенов с одного аккаунта на другой.

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ExternalCallVulnerable {
    address public externalTokenContract;

    constructor(address _externalTokenContract) {
        externalTokenContract = _externalTokenContract;
    }

    function exchangeTokens(uint256 amount) public {
        IERC20(externalTokenContract).transferFrom(msg.sender, address(this), amount);
    }
}

В этом контракте функция ExchangeTokens вызывает функцию TransferFrom внешнего контракта. Однако здесь есть потенциальная проблема: внешний контракт может содержать вредоносный код, либо его логика может не соответствовать ожиданиям, что приведет к потере средств или другим нежелательным последствиям.

Демонстрация атаки

Злоумышленник может развернуть вредоносный контракт токена ERC20 и передать этот адрес контракта нашему контракту. Вредоносный контракт может содержать дополнительную логику в функции TransferFrom, например, вызов других функций нашего контракта при передаче токенов или выполнение каких-либо несанкционированных операций.

Язык кода:javascript
копировать
// Вредоносный контракт Пример
contract MaliciousToken is IERC20 {
    function transferFrom(address, address, uint256) public override returns (bool) {
        // Обычная логика передачи токена...

        // Выполнять дополнительные вредоносные действия, такие как вызов другой функции в контракте.
        ExternalCallVulnerable(0x...).someUnsafeFunction();

        return true;
    }
}

Когда пользователь пытается обменять токены во вредоносном контракте через наш контракт, будет вызвана функция TransferFrom вредоносного контракта для выполнения вредоносных операций.

решение

Чтобы снизить риски, вызванные внешними вызовами, мы можем принять следующие меры:

  • 1、проверка код: выполнить тщательную проверку внешнего контракта, прежде чем разрешить его вызов. кода, убедитесь, что его логика соответствует ожиданиям и не содержит вредоносного кода.
  • 2、Механизм белого список: Разрешено вызывать только список проверенных, доверенных контрактов. Таким образом, даже если появится новый вредоносный контракт, его нельзя будет вызвать через наш контракт.
  • 3、Используйте безопасные библиотеки: воспользуйтесь преимуществами стандартизированных интерфейсов таких библиотек, как OpenZeppelin, которые обычно учитывают проблемы совместимости.
  • 4. Ограничить вызов вызова: избегайте повторного вызова других внешних контрактов при вызове внешнего контракта.,предотвратитьрекурсивный вызовпривести кизатаковать。
  • 5、Мониторинг событий и обработка Исключения: при вызове внешнего контракта отслеживайте возвращаемое значение и исключение, чтобы убедиться, что вызов успешен и не происходит аномального поведения.

Ниже приведен улучшенный контракт Пример, который реализует Механизм. белого списка:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function transferFrom(address, address, uint256) external returns (bool);
}

contract SafeExternalCall {
    mapping(address => bool) public approvedContracts;
    address public externalTokenContract;

    constructor(address _externalTokenContract) {
        approveContract(_externalTokenContract);
        externalTokenContract = _externalTokenContract;
    }

    function exchangeTokens(uint256 amount) public {
        require(approvedContracts[externalTokenContract], "Contract not approved");
        IERC20(externalTokenContract).transferFrom(msg.sender, address(this), amount);
    }

    function approveContract(address contractAddress) public {
        approvedContracts[contractAddress] = true;
    }
}

существоватьсмарт-контрактразвиватьсередина,Вызов ненадежных внешних контрактов является распространенной точкой риска. Это потому, что,Когда вы вызываете функцию другого контракта,Вы фактически выполняете код этого контракта.,И это может привести к поведению, которого вы не ожидали,В том числе злонамеренное поведение. Ниже я проиллюстрирую этот риск с помощью примера.,и предложить соответствующие стратегии смягчения последствий.

лазейкидоговор Пример

Предположим, у нас есть смарт-контракт,Он позволяет пользователям выполнять определенные задачи, вызывая внешний контракт.,Например, обмен токенов. здесь,Мы предполагаемвнешнийдоговорпредоставилодинtransferFromфункция,Используется для перевода токенов с одного аккаунта на другой.

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ExternalCallVulnerable {
    address public externalTokenContract;

    constructor(address _externalTokenContract) {
        externalTokenContract = _externalTokenContract;
    }

    function exchangeTokens(uint256 amount) public {
        IERC20(externalTokenContract).transferFrom(msg.sender, address(this), amount);
    }
}

в этом контракте,exchangeTokensфункциявызов了внешнийдоговоризtransferFromфункция。Однако,Здесь есть потенциальная проблема: внешние контракты могут содержать вредоносный код.,или Логика может не соответствовать ожиданиям,Что приводит к потере средств и другим неблагоприятным последствиям.

Демонстрация атаки

Злоумышленник может развернуть вредоносный контракт токена ERC20.,ипомести этодоговорадрес传递给насиздоговор。злонамеренныйдоговорвозможныйсуществоватьtransferFromфункциясередина Содержит дополнительныеизлогика,Например, при передаче токенов,Вызов другой функции в нашем контракте,Злоумышленник выполняет некоторые несанкционированные операции.

Язык кода:javascript
копировать
// Вредоносный контракт Пример
contract MaliciousToken is IERC20 {
    function transferFrom(address, address, uint256) public override returns (bool) {
        // Обычная логика передачи токена...

        // Выполнять дополнительные вредоносные действия, такие как вызов другой функции в контракте.
        ExternalCallVulnerable(0x...).someUnsafeFunction();

        return true;
    }
}

Когда пользователь пытается обменять токены из вредоносного контракта через наш контракт,злонамеренныйдоговоризtransferFromфункциябудет вызван,Выполнять вредоносные действия.

Улучшения безопасности

Чтобы снизить риски, вызванные внешними вызовами, мы можем принять следующие меры:

  1. проверка кода:существовать允许вызоввнешнийдоговор До,Дайте ему тщательную проверку кода, убедитесь, что его логика соответствует ожиданиям и не содержит вредоносного кода.
  2. Механизм белого списка:只允许вызов经过验证из、заслуживающий доверияиздоговорсписок。так,Даже если появится новый вредоносный контракт,Его также нельзя вызвать через наш контракт.
  3. Используйте безопасные библиотеки:Используйте, например.OpenZeppelinждать Безопасность Библиотекасерединаиз Стандартизированный интерфейс,В этих интерфейсах обычно уже учтены проблемы совместимости.
  4. Ограничить глубину вызова:избегатьсуществоватьвызоввнешнийдоговорчас再次вызов Что他внешнийдоговор,предотвратитьрекурсивный вызовпривести кизатаковать。
  5. Мониторинг событий и обработка исключений:существоватьвызоввнешнийдоговорчас,Слушайте возвращаемые значения и исключения,Убедитесь, что вызов успешен и не происходит необычного поведения.

Ниже приведен улучшенный контракт Пример, который реализует Механизм. белого списка:

Язык кода:javascript
копировать
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function transferFrom(address, address, uint256) external returns (bool);
}

contract SafeExternalCall {
    mapping(address => bool) public approvedContracts;
    address public externalTokenContract;

    constructor(address _externalTokenContract) {
        approveContract(_externalTokenContract);
        externalTokenContract = _externalTokenContract;
    }

    function exchangeTokens(uint256 amount) public {
        require(approvedContracts[externalTokenContract], "Contract not approved");
        IERC20(externalTokenContract).transferFrom(msg.sender, address(this), amount);
    }

    function approveContract(address contractAddress) public {
        approvedContracts[contractAddress] = true;
    }
}

В этом улучшенном контракте,Мы добавилиapprovedContractsкартографирование,Используется для хранения утвержденных адресов внешних контрактов. Только если адрес внешнего контракта внесен в белый список,Его можно вызвать через наш договор.

благодаря этим улучшениям,Мы можем значительно снизить риск, вызванный вызовом ненадежных внешних контрактов. Однако,в практическом применении,Также необходимо продолжать уделять внимание новым угрозам безопасности и передовому опыту.,Поддерживать безопасность характера контракта.

12. Генерация случайных чисел

Как правило, генерацию случайных чисел в блокчейне обычно сложно осуществить, и она зависит от предсказуемых факторов, таких как хеши блоков. Это может привести к тому, что злоумышленники смогут предсказать результаты. Ниже приведены некоторые распространенные сценарии.

1. Прогнозируйте случайные числа
Язык кода:javascript
копировать
contract GuessTheNumber {
    function guess(bool isHigher) public {
        uint256 randomNumber = block.timestamp % 100; // Временная метка используется здесь как источник случайных чисел.
        if ((randomNumber > 50) == isHigher) {
            // Если игрок угадал правильно, награда распределяется
        }
    }
}

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

2. Интерактивная генерация случайных чисел
Язык кода:javascript
копировать
contract Auction {
    function endAuction() public {
        uint256 random = ExternalRandomService.getLastBlockHash() % bidders.length;
        // Предположим, что участники торгов представляют собой массив, и для выбора победителя торгов используется случайный метод.
    }
}

Злоумышленник может наблюдать за транзакцией контракта, которая вот-вот завершит аукцион, а затем отправить собственную транзакцию до того, как контракт вызовет getLastBlockHash(), влияя на хэш блока и, следовательно, на окончательное случайное число.

3. Опора на оракулов
Язык кода:javascript
копировать
contract Game {
    function play() public {
        uint256 random = OracleService.getRandomNumber();
        // Используйте случайные числа, предоставленные оракулами
    }
}

Если сервис оракула контролируется злоумышленником, он может предоставить ложные случайные числа и повлиять на исход игры.

Предложения

Для решения вышеперечисленных проблем можно использовать несколько стратегий:

  • 1. Используйте проверенных оракулов. Выберите надежного поставщика услуг оракула, предпочтительно прошедшего аудит и имеющего хорошую историю.
  • 2. Генерация многофакторных случайных чисел. Генерация случайных чисел путем объединения нескольких факторов, которые трудно предсказать, таких как сложность блока, информация заголовка блока и энтропия, предоставляемая вне цепочки.
  • 3. Временная задержка. Добавление задержки между генерацией случайных чисел и использованием случайных чисел затрудняет злоумышленникам прогнозирование результатов в реальном времени.
  • 4. Генерация случайных чисел вне цепочки. Используйте службу генерации случайных чисел вне цепочки, а затем отправьте результаты в цепочку через оракул.
  • 5. Технология шифрования: используйте технологию криптографии.,Идентичное шифрование и доказательство с нулевым разглашением,чтобы обеспечить Генерация случайных Конфиденциальность процесса чисел и Безопасность.

13. Эффективность хранения и вычислений

Язык кода:javascript
копировать
Неправильная структура храненияили Вычислительно интенсивные операции могут привести к высокимGasрасходыи Узкое место в производительности。
Пример сценария: частое чтение и запись больших массивов

Предположим, вы создаете систему голосования, в которой каждое предложение имеет независимый счетчик голосов. Чтобы добиться этого, вы можете сначала рассмотреть возможность использования карты, где ключами являются идентификаторы предложений, а значениями — массив, хранящий адреса всех избирателей, проголосовавших за это предложение.

Язык кода:javascript
копировать
// Неправильная структура хранения
contract VotingSystem {
    mapping(uint => address[]) public voters;

    function vote(uint proposalId, address voter) public {
        voters[proposalId].push(voter);
    }

    function getVotesCount(uint proposalId) public view returns (uint) {
        return voters[proposalId].length;
    }
}
Проблемы
  • 1. Стоимость газа высока: каждый раз, когда кто-то голосует, массиву необходимо перераспределить пространство памяти для размещения новых элементов, что потребляет много газа.
  • 2. Узкое место в производительности: чтение и запись больших массивов станет очень медленным.,Потому что каждая операция чтения и записи требует обхода всего массива.
Решение: Оптимизировать структуру хранения

Предложения по оптимизации Чтобы снизить затраты на газ и повысить производительность, мы можем перепроектировать структуру данных, чтобы использовать сопоставление для отслеживания того, проголосовал ли каждый избиратель за определенное предложение, вместо сохранения массива избирателей.

Язык кода:javascript
копировать
// Оптимизированная структура хранения
contract OptimizedVotingSystem {
    mapping(uint => mapping(address => bool)) public hasVoted;

    function vote(uint proposalId, address voter) public {
        require(!hasVoted[proposalId][voter], "Already voted");
        hasVoted[proposalId][voter] = true;
    }

    function getVotesCount(uint proposalId) public view returns (uint) {
        uint count;
        for (address voter = address(1); voter != address(0); voter = address(uint(voter) + 1)) {
            if (hasVoted[proposalId][voter]) {
                count++;
            }
        }
        return count;
    }
}
Инструкция по улучшению
  • 1. Сократите расходы на газ: использование карт для отслеживания статуса голосования более эффективно, чем ведение массивов,Поскольку сопоставленные операции (такие как вставка и поиск) обычно выполняются быстрее.,Потребляет меньше газа.
  • 2. Повышение производительности. Операция сопоставления имеет почти постоянную временную сложность O(1) и не замедляется по мере увеличения объема данных.
На что следует обратить внимание

Хотя использование карты может значительно повысить эффективность, перебор всех адресов для подсчета голосов в функции getVotesCount по-прежнему неэффективен. На практике вы можете ввести дополнительные сопоставления или переменные для отслеживания общего количества голосов по каждому предложению, чтобы избежать полного обхода адресного пространства.

Язык кода:javascript
копировать
// Дальнейшая оптимизация
contract FurtherOptimizedVotingSystem {
    mapping(uint => mapping(address => bool)) public hasVoted;
    mapping(uint => uint) public votesCount;

    function vote(uint proposalId, address voter) public {
        require(!hasVoted[proposalId][voter], "Already voted");
        hasVoted[proposalId][voter] = true;
        votesCount[proposalId]++;
    }

    function getVotesCount(uint proposalId) public view returns (uint) {
        return votesCount[proposalId];
    }
}

так,Просто обновляйте счетчик голосов каждый раз, когда вы голосуете.,Затраты на газ значительно сокращаются, а скорость запросов увеличивается. в смарт-контрактразвивать,Разумный дизайн и оптимизированная структура хранения имеют решающее значение для снижения затрат и повышения производительности.

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.