Back to Blog

Отчет об аудите безопасности NearOinDao

Code Auditing
10 декабря 2021 г.
31 min read

Отчет о манифесте

Элемент Описание
Клиент Oinfinance
Цель NearOinDao

История версий

Версия Дата Описание
1.0 04 дек. 2021 г. Первый выпуск

1. Введение

1.1 О целевых смарт-контрактах

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

Информация Описание
Тип Смарт-контракт
Язык Rust
Подход Полуавтоматическая и ручная проверка

Репозитории, которые были проверены, включают NearOinDao ^1

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

До и во время аудита

После

Проект SHA коммита
NearOinDao 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a

1.2 Модель безопасности

Для оценки риска мы следуем стандартам или рекомендациям, которые широко приняты как в индустрии, так и в академических кругах, включая методологию оценки рисков OWASP ^2 и перечень распространенных слабых мест (CWE) ^3. Соответственно, серьезность рисков в этом отчете классифицируется по четырем категориям: Высокая (High), Средняя (Medium), Низкая (Low) и Неопределенная (Undetermined).

2. Результаты

Всего мы обнаружили 22 потенциальные проблемы в смарт-контракте. У нас также есть 12 рекомендаций:

  • Высокий риск: 19

  • Средний риск: 2

  • Низкий риск: 1

  • Рекомендации: 12

Подробности приведены в следующих разделах.

ID Серьезность Описание Категория Статус
1 Высокая Логическая ошибка при изменении self.liquidation_line Программная безопасность Подтверждено и исправлено
2 Высокая Функция ликвидации может не работать Программная безопасность Подтверждено и исправлено
3 Высокая Логическая ошибка при установке временной метки открытия контракта Программная безопасность Подтверждено и исправлено
4 Высокая Состояние контракта не откатывается, если кросс-контрактная транзакция не удалась Программная безопасность Подтверждено и исправлено
5 Высокая Любой может добавить баланс вознаграждения Безопасность DeFi Подтверждено и исправлено
6 Высокая Любой может добавить баланс вознаграждения стабильного пула Безопасность DeFi Подтверждено и исправлено
7 Высокая Любой может сжечь монеты других пользователей Безопасность DeFi Подтверждено и исправлено
8 Высокая Любой может добавить баланс своего аккаунта Безопасность DeFi Подтверждено и исправлено
9 Высокая Оракул не проверяет временной интервал Безопасность DeFi Подтверждено и исправлено
10 Высокая Слишком большой временной интервал оракула Безопасность DeFi Подтверждено и исправлено
11 Высокая Нет оракула для цены Oin Безопасность DeFi Подтверждено и исправлено
12 Высокая Пользователи могут получить дополнительное вознаграждение Безопасность DeFi Подтверждено и исправлено
13 Высокая Пользователи могут платить меньше комиссии за стейблкоин Безопасность DeFi Подтверждено и исправлено
14 Средняя Мультиподписной запрос может быть подтвержден с относительно низким коэффициентом подтверждения Безопасность DeFi Подтверждено и исправлено
15 Средняя Неточный расчет количества блоков в год Безопасность DeFi Подтверждено и исправлено
16 Высокая Неверное количество доступных для минта монет Безопасность DeFi Подтверждено и исправлено
17 Высокая Оплата комиссии за стейблкоин может привести к потере депозитных токенов пользователя Безопасность DeFi Подтверждено и исправлено
18 Высокая Некорректный коэффициент стейкинга Безопасность DeFi Подтверждено и исправлено
19 Низкая Количество вознаграждаемых монет может превышать лимит Безопасность DeFi Подтверждено и исправлено
20 Высокая Один и тот же белый список для пользователей с разными привилегиями Безопасность DeFi Подтверждено и исправлено
21 Высокая Отсутствие проверки адреса комиссии за стейблкоин Безопасность DeFi Подтверждено и исправлено
22 Высокая Параметр total_reward вознаграждаемой монеты может быть изменен мультиподписными менеджерами Безопасность DeFi Подтверждено и исправлено
23 - Избыточное утверждение Рекомендация Подтверждено и исправлено
24 - Повторный учет линии ликвидации Рекомендация Подтверждено и исправлено
25 - Избыточная проверка белого списка Рекомендация Подтверждено и исправлено
26 - Неиспользуемая функция Рекомендация Подтверждено и исправлено
27 - Избыточный код Рекомендация Подтверждено и исправлено
28 - Противоречие между названием функции и ее реализацией Рекомендация Подтверждено и исправлено
29 - Избыточный код Рекомендация Подтверждено и исправлено
30 - Точность вычислений может быть улучшена Рекомендация Подтверждено и исправлено
31 - Система может не записывать ранее полученную цену Рекомендация Подтверждено и исправлено
32 - Разрывное распределение токенов обеспечения при ликвидации Рекомендация Подтверждено и исправлено
33 - Оптимизация точности вычислений не требуется Рекомендация Подтверждено и исправлено
34 - Риск централизованного дизайна Рекомендация Принято

2.1 Программная безопасность

2.1.1 Потенциальная проблема 1: Два разных атрибута для одного и того же использования

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Два атрибута (self.cost и self.liquidation_line) представляют одно и то же состояние контракта — линию ликвидации пользователя. Они используются в разных функциях контракта (Листинг 2.1 и Листинг 2.2). Однако self.liquidation_line может быть изменена с помощью функции set_liquidation_line, в то время как self.cost изменить нельзя. В этом случае, если self.liquidation_line изменяется, self.cost сохраняет исходное значение. Это может повлиять на логику функции assert_user_ratio (Листинг 2.1).

pub(crate) fn assert_user_ratio(&self) {
        let user_ratio = self.internal_user_ratio(env::predecessor_account_id());
        if user_ratio != 0 {
            assert!(user_ratio >= self.cost, "User ratio less than standard.");
        }
    }

Листинг 2.1: assert_user_ratio:lib.rs

// TODO ликвидация
    #[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        assert!(self.is_liquidation_paused(), "{}", SYSTEM_PAUSE);
        let ratio = self.internal_user_ratio(account.clone());
        assert!(ratio > 0, "Нет текущего залога");
        assert!(ratio <= self.liquidation_line, "Не на линии очистки");
        ...

Листинг 2.2: internal_can_mint_amount:lib.rs

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

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

2.1.2 Потенциальная проблема 2: Неверное распределение ликвидационного вознаграждения

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-4. Аккаунт отправителя ликвидации и аккаунт владельца контракта могут быть не зарегистрированы (строки 193 и 206 Листинга 2.3). В этом случае, когда отправитель пытается провести ликвидацию, транзакция не может быть выполнена успешно из-за возникновения исключения, вызванного тем, что аккаунты не зарегистрированы.

pub(crate) fn personal_liquidation_token(&mut self, send_id: AccountId, account_id: AccountId, liquidation_gas: Balance, surplus_token: Balance, liquidation_fee: Balance) {
        //self.owner_id
        let coin_id = ST_NEAR.to_string();
        let mut sys_reward_coin = self.internal_get_reward_coin(coin_id.clone());
        
        let account_reward_key_o = self.get_staker_reward_key(send_id.clone(), coin_id.clone());
        let user_reward_coin_o = self.internal_get_account_reward(send_id.clone(), coin_id.clone());
        
        self.account_reward.insert(
            &account_reward_key_o,
            &UserReward {
                index:  user_reward_coin_o.index,
                reward: user_reward_coin_o.reward.checked_add(liquidation_gas).expect(ERR_ADD),
            },
        );
        
        let account_reward_key_t = self.get_staker_reward_key(account_id.clone(), coin_id.clone());
        let user_reward_coin_t = self.internal_get_account_reward(account_id.clone(), coin_id.clone());

        if surplus_token > 0 {
            self.account_reward.insert(
                &account_reward_key_t,
                &UserReward {
                    index:  user_reward_coin_t.index,
                    reward: user_reward_coin_t.reward.checked_add(surplus_token).expect(ERR_ADD),
                },
            );
        }

        let account_reward_key_s = self.get_staker_reward_key(self.owner_id.clone(), coin_id.clone());
        let user_reward_coin_s = self.internal_get_account_reward(self.owner_id.clone(), coin_id.clone());

        self.account_reward.insert(
            &account_reward_key_s,
            &UserReward {
                index:  user_reward_coin_s.index,
                reward: user_reward_coin_s.reward.checked_add(liquidation_fee).expect(ERR_ADD),
            },
        );
       
        sys_reward_coin.total_reward = sys_reward_coin
            .total_reward
            .checked_add(liquidation_gas).expect(ERR_ADD)
            .checked_add(liquidation_fee).expect(ERR_ADD)
            .checked_add(surplus_token).expect(ERR_ADD);

        self.reward_coins.insert(&coin_id, &sys_reward_coin);
    }

}

Листинг 2.3: personal_liquidation_token:reward.rs

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

Предложение I Выполнять проверку существования аккаунта отправителя ликвидации и аккаунта владельца контракта в начале функции ликвидации.

2.1.3 Потенциальная проблема 3: Сохранение блока_timestamp в closed_time при открытии системы

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. env::block_time_stamp() не должно сохраняться в self.closed_time при вызове функции internal_open.

#[private]
    pub fn internal_open(&mut self) {
        self.closed_time = env::block_timestamp();
        self.open_stake();
        self.open_redeem();
        self.open_claim_reward();
        self.open_liquidation();
        self.open_stable();
        log!(
            "{} открывает систему в {}",
            env::predecessor_account_id(),
            self.closed_time
        );
    }

Листинг 2.4: internal_open:esm.rs

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

Предложение I Мы предлагаем создать новое состояние контракта под названием self.opening_time и присваивать ему значение env::block_timestamp() при вызове функции открытия контракта.

2.1.4 Потенциальная проблема 4: Состояние контракта не откатывается при сбое вызовов кросс-функций

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Процесс storage_deposit и ft_transfer может дать сбой во время вызовов кросс-контрактных функций. Мы не можем гарантировать, что перевод всегда будет выполнен правильно. Функция обратного вызова (callback) не откатывает состояние контракта, если вызов завершился неудачей.

#[private]
    pub fn storage_deposit_callback(&mut self) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {
                log!("Перевод успешен");
            }
            PromiseResult::Failed => {
                log!("Перевод не удался");
            }
        }
    }

Листинг 2.5: storage_deposit_callback:ft.rs

#[private]
    pub fn liquidation_transfer_callback(&mut self) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {
                log!("Перевод успешен");
            }
            PromiseResult::Failed => {
                log!("Перевод не удался");
            }
        }
    }

Листинг 2.6: liquidation_transfer_callback:ft.rs

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

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

2.2 Безопасность DeFi

2.2.1 Потенциальная проблема 5: В inject_reward отсутствует контроль доступа

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция inject_reward является общедоступной. Любой может вызвать эту функцию, чтобы добавить баланс вознаграждения в контракт.

pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Сумма не равна transfer_amount"
            );
        }
        ...
    }

Листинг 2.7: inject_reward:pool.rs

Влияние Любой может добавить произвольный баланс к вознаграждению контракта.

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

2.2.2 Потенциальная проблема 6: В inject_sp_reward отсутствует контроль доступа

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция inject_sp_reward является общедоступной. Любой может вызвать её, чтобы добавить баланс вознаграждения стабильного пула в контракт.

pub fn inject_sp_reward(&mut self, _amount: U128, sender_id: ValidAccountId) {
        self.reward_sp = self.reward_sp + u128::from(_amount);

        log!(
            "{} добавил sp_reward {} текущая сумма {}",
            sender_id,
            u128::from(_amount),
            self.reward_sp
        );
    }

Листинг 2.8: inject_sp_reward:stablepool.rs

Влияние Любой может добавить произвольный баланс к вознаграждению стабильного пула.

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

2.2.3 Потенциальная проблема 7: В burn_coin отсутствует контроль доступа

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция burn_coin является общедоступной. Любой может вызвать её, чтобы сжечь монеты любого другого пользователя.

pub fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        assert!(self.is_redeem_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(sender_id);
        self.assert_is_poked();
        self.accured_token(sender_id.clone());
        ...
    }

Листинг 2.9: burn_coin:lib.rs

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

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

2.2.4 Потенциальная проблема 8: В deposit_token отсутствует контроль доступа

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция deposit_token является общедоступной. Любой может вызвать её, чтобы увеличить баланс своего счета.

pub fn deposit_token(&mut self, amount: u128, _sender_id: ValidAccountId) {
        self.assert_is_poked();
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let _amount = u128::from(amount);
        let sender_id = AccountId::from(_sender_id);
        . . .
    }

Листинг 2.10: deposit_token:lib.rs

Влияние Злоумышленники могут вызвать эту функцию для увеличения баланса своего аккаунта.

Предложение I Эту функцию следует сделать приватной, так как она вызывается внутренне после приема депонированных токенов.

2.2.5 Потенциальная проблема 9: Оракул не проводит проверку времени

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция assert_is_poked в oracle.rs проверяет только то, что цена токена не равна нулю. Это не имеет смысла, так как цена токена постоянно меняется.

pub(crate) fn assert_is_poked(&self) {
        assert!(self.token_price != 0, "Цена оракула не получена.");
    }

Листинг 2.11: assert_is_poked:oracle.rs

Влияние Эта проблема касается ценовых оракулов. Если цена токена не обновлялась (не «тыкалась») в течение долгого времени, утверждение все равно может пройти успешно, и связанная транзакция будет выполнена с устаревшей ценой.

Предложение I Контракт должен установить допустимый временной интервал для получения цены.

2.2.6 Потенциальная проблема 10: Ненадлежащий интервал получения цены оракулом

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Константа POKE_INTERVAL_TIME, определенная в types.rs, сейчас означает 1000 дней. Этот интервал кажется слишком большим. Требуется разумное значение.

pub const POKE_INTERVAL_TIME: u64 = 86_400_000_000_000_000;

Листинг 2.12: types.rs

Влияние Временной интервал для получения цены неуместен.

Предложение I Переустановите интервал времени для получения цены на разумное значение.

2.2.7 Потенциальная проблема 11: Отсутствует утверждение (Assert) для Oin_Price

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция не проверяет, была ли получена цена oin_token, так как стейбл-комиссия пользователя рассчитывается исходя из self.oin_price.

pub fn internal_user_stable(&self, account: AccountId) -> u128 {
        let user_stable = self.account_stable.get(&account).expect("ошибка");
        let allot = self.get_account_allot(account.clone()); 
        let coin = self
            .account_coin
            .get(&account)
            .expect("ошибка")
            .checked_add(allot.0)
            .expect(ERR_ADD);
        let current_block_number = env::block_timestamp().checked_div(INIT_BLOCK_TIME).expect(ERR_DIV);
        user_stable
            .saved_stable
            .checked_add(
                self.stable_fee_rate//16
                    .checked_div(BLOCK_PER_YEAR)
                    .expect(ERR_DIV)
                    .checked_mul(current_block_number as u128 - user_stable.block)
                    .expect(ERR_MUL)
                    .checked_mul(coin)//8
                    .expect(ERR_MUL)
                    .checked_div(self.oin_price)//8
                    .expect(ERR_DIV)
                    .checked_div(ONE_COIN)//8
                    .expect(ERR_DIV),
            )
            .expect(ERR_ADD)
    }

Листинг 2.13: internal_user_stable:lib.rs

Влияние Устаревшая цена OIN может привести к манипуляциям с ценой, так как не проверяется свежесть цены, полученной от оракула.

Предложение I Добавьте утверждение self.assert_is_poked(); перед расчетом стейбл-комиссии пользователя.

2.2.8 Потенциальная проблема 12: Пользователи могут получать больше вознаграждения за майнинг при стейкинге токенов

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Вознаграждение рассчитывается неточно. Функция internal_get_saved_reward вызывается для расчета специфического вознаграждения за майнинг пользователя с t0 до t1 по следующей формуле:

Обратите внимание, что account_allot.token — это вознаграждение в залоге, добавленное в результате ликвидации другого пользователя. Однако ликвидация может произойти в любое время с t0 по t1. Например, пользователь внес 100 токенов в день 0. На 999-й день инициируется ликвидация другого пользователя, из-за чего account_allot.token может увеличиться до 1000.

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

// TODO [OK] Расчет вознаграждения
    pub(crate) fn internal_get_saved_reward(
        &self,
        staker: AccountId,      
        reward_coin: AccountId, 
    ) -> u128 {
        let reward_coin_ins = self.internal_get_reward_coin(reward_coin.clone());
        let (stake_token_num, _) = self.staker_debt_of(staker.clone());

        if let Some(user_reward) = self
            .account_reward
            .get(&self.get_staker_reward_key(staker.clone(), reward_coin.clone()))
        {
            user_reward
                .reward
                .checked_add(
                    U256::from(
                        reward_coin_ins
                            .index
                            .checked_sub(user_reward.index)
                            .expect(ERR_SUB),
                    )
                    .checked_mul(U256::from(stake_token_num))
                    .expect(ERR_MUL)
                    .checked_div(U256::from(reward_coin_ins.double_scale))
                    .expect(ERR_DIV)
                    .as_u128(),
                )
                .expect(ERR_ADD)
        } else {
            0
        }
    }

Листинг 2.14: internal_get_saved_reward:views.rs

pub fn staker_debt_of(&self, staker: AccountId) -> (u128, u128) {
        if let Some(token) = self.account_token.get(&staker) {
            let coin = self.account_coin.get(&staker).expect(ERR_NOT_REGISTER);
            let allot = self.get_account_allot(staker.clone());
            (token + allot.1, coin + allot.0)
        } else {
            (0, 0)
        }
    }

Листинг 2.15: staker_debt_of:views.rs

Влияние Пользователи могут получать дополнительные вознаграждения.

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

2.2.9 Потенциальная проблема 13: Пользователи могут платить меньше стабильной комиссии

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Допустим, пользователь выпускает 1000 USDO в день 0, и stable_fee_rate в то время составляет 0.01 оин/монета/день. Если пользователь возвращает 1000 USDO в день 100, а ставка stable_fee_rate не менялась за эти 100 дней, то комиссия, которую он должен заплатить, составляет 0.01 Оин/монета/день * 1000 монет * 100 дней = 1000 Оин. Однако, если владелец установил stable_fee_rate = 0.005 оин/монета/день на 99-й день, в это время пользователю нужно заплатить только 0.005 Оин/монета/день * 1000 монет * 100 дней = 500 Оин. Фактически же правильная комиссия должна быть следующей: (0.01 Оин/монета/день * 1000 монет * 99 дней) + (0.005 Оин/монета/день * 1000 монет * 1 день) = 990 Оин + 5 Оин = 995 Оин.

В этом случае 495 Оин пользователями не выплачиваются.

// TODO [OK]
    pub fn set_stable_fee_rate(&mut self, fee_rate: U128) {
        self.assert_param_white();
        self.update_stable_index();
        assert!(fee_rate.0 <= INIT_MAX_STABLE_FEE_RATE, "Превышение максимального значения");
        self.stable_fee_rate = fee_rate.into();
        log!("Установлена ставка stable fee {}", fee_rate.0);
    }

Листинг 2.16: set_stable_fee_rate:dparam.rs

pub fn update_stable_index(&mut self) {
    }

Листинг 2.17: update_stable_index:stablefee.rs

Влияние С пользователей контракта может взиматься меньшая комиссия за стейблкоин.

Предложение I Реализуйте системный индекс стейбл-комиссии аналогично расчету reward_coin в этом контракте. Убедитесь, что системный индекс стейбл-комиссии обновляется при вызове set_stable_fee_rate, liquidation и update_stable_fee пользователями контракта.

2.2.10 Потенциальная проблема 14: Необоснованный коэффициент подтверждения мультиподписных запросов

Элемент Описание
Статус Подтверждено и исправлено

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

pub(crate) fn is_num_enough(&self, request_id: RequestId) -> bool {
        let request = self.requests.get(&request_id).unwrap();
        let confirmations = self.confirmations.get(&request_id).unwrap();

        let num_confirmrations = request.num_confirm_ratio * (request.mul_white_num);
        log!(
            "число подтверждений {}, нужно {}",
            confirmations.len() as u32 * 100,
            num_confirmrations
        );

        (confirmations.len() as u64) * 100 >= num_confirmrations
    }

Листинг 2.18: is_num_enough:multisign.rs

pub fn add_request_only(&mut self, request: MultiSigRequest) -> RequestId {
        self.assert_mul_white();
        ...

        let request_added = MultiSigRequestWithSigner {
            signer_pk: env::signer_account_pk(),
            added_timestamp: env::block_timestamp(),
            confirmed_timestamp: 0,
            request: request,
            is_executed: false,
            cool_down: self.request_cooldown,
            mul_white_num: self.mul_white_num(),
            num_confirm_ratio: self.num_confirm_ratio,
        };

        self.requests.insert(&self.request_nonce, &request_added);
        ...
    }

Листинг 2.19: add_request_only:multisign.rs

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

Предложение I Рассмотрите возможность использования текущего количества пользователей с правами мультиподписи в состоянии контракта для расчета коэффициента подтверждения.

2.2.11 Потенциальная проблема 15: Неверное количество блоков в год

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Учитывая, что на основной сети протокола NEAR блок генерируется каждую секунду, количество блоков в год должно быть 31 536 000 (365 дней), а не 31 104 000 (360 дней).

pub const BLOCK_PER_YEAR: u128 = 31104000;

Листинг 2.20: types.rs

Влияние Неточная константа BLOCK_PER_YEAR сделает результаты вычислений с ее использованием несоответствующими действительности.

Предложение I Измените BLOCK_PER_YEAR на 31 536 000.

2.2.12 Потенциальная проблема 16: Некорректный расчет максимального количества USDO для минта

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. allot_token.0 представляет выделенный долг. При расчете доступной суммы минта для USDO этот долг не должен учитываться. В противном случае пользователь с очень высоким долгом может выпустить огромное количество USDO.

pub(crate) fn internal_can_mint_amount(&self, account: AccountId) -> u128 {
        self.assert_is_poked();
        let token = self.account_token.get(&account).expect(ERR_NOT_REGISTER);
        let guarantee = self.guarantee.get(&account).expect(ERR_NOT_REGISTER);
        let allot_token = self.get_account_allot(account.clone());

        let max_usdo = (U256::from(token)
            .checked_add(U256::from(allot_token.1))
            .expect(ERR_ADD))
        .checked_mul(U256::from(self.token_price))
        .expect(ERR_MUL)
        .checked_div(U256::from(self.liquidation_line))
        .expect(ERR_DIV)
        .checked_div(U256::from(INIT_STABLE_INDEX))
        .expect(ERR_DIV)
        .checked_add(U256::from(allot_token.0))
        .expect(ERR_ADD)
        .checked_sub(U256::from(guarantee))
        .unwrap_or(U256::from(0))
        .as_u128();
        
        ...
    }

Листинг 2.21: internal_can_mint_amount:lib.rs

Влияние Пользователи могут выпускать дополнительные USDO при вызове функции mint_coin.

Предложение I allot_token.0, представляющий выделенный долг, не должен учитываться как доступные выпущенные USDO.

2.2.13 Потенциальная проблема 17: Некорректное обращение со стабильной комиссией пользователя

Элемент Описание
Статус Подтверждено и исправлено (связанная логика удалена)

Описание Эта проблема появилась не позднее коммита-1. Когда пользователи вызывают функцию burn_coin, стабильная комиссия оплачивается токеном 'OIN', а не 'ST_NEAR'. Тем не менее, контракт уменьшает баланс токенов стейкинга пользователя, что не является верным.

pub(crate) fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        ...
            assert!(usdo >= amount.into(), "Недостаточная сумма");
            let token = self.account_token.get(&sender_id.clone()).expect(ERR_NOT_REGISTER);
            self.internal_burn(sender_id.clone(), amount.into());
   
            self.total_token = self.total_token.checked_sub(unpaid_fee.into()).expect(ERR_SUB);
            self.account_token.insert(
                &sender_id.clone(),
                &token.checked_sub(unpaid_fee.into()).expect(ERR_SUB),
            );
        ...
        
    }

Листинг 2.22: burn_coin:lib.rs

Влияние Токены стейкинга пользователей могут быть уменьшены из-за некорректного обращения со стабильной комиссией.

Предложение I Используйте правильный токен для оплаты стабильных комиссий.

2.2.14 Потенциальная проблема 18: Некорректный системный коэффициент

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Если total_coin = 0, коэффициент должен быть +∞. Установка его в 0 неверна.

pub(crate) fn internal_sys_ratio(&self) -> u128 {
        self.assert_is_poked();
        let token_usd = U256::from(self.total_token)
            .checked_mul(U256::from(self.token_price))
            .expect(ERR_MUL); /* 32 */
        let total_coin = self.total_coin + self.total_guarantee;
        if total_coin == 0 {
            0
        } else {
            token_usd
                .checked_div(U256::from(STAKE_RATIO_BASE))
                .expect(ERR_DIV)
                .checked_div(U256::from(total_coin))
                .expect(ERR_DIV)
                .as_u128()
        }
    }

Листинг 2.23: internal_sys_ratio:lib.rs

Влияние Система, скорее всего, будет остановлена из-за некорректного коэффициента.

Предложение I Измените условие if с total_coin = 0 на token_usd = 0.

2.2.15 Потенциальная проблема 19: Количество вознаграждаемых монет может превышать верхний предел

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Когда сейчас существует 20 вознаграждаемых монет, утверждение (assert) в строке 131 Листинга 2.24 может пройти успешно. В этом случае можно добавить еще одну вознаграждаемую монету, и общее количество вознаграждаемых монет может превысить REWARD_UPPER_BOUND.

pub(crate) fn internal_add_reward_coin(&mut self, coin: RewardCoin) {
        assert!(
            self.reward_coins.len() <= REWARD_UPPER_BOUND,
            "Слот валюты заполнен, пожалуйста, измените информацию о другой валюте соответствующим образом",
        );

        match self.reward_coins.get(&coin.token) {
            Some(_) => {
                env::panic(b"Данная валюта уже была добавлена, пожалуйста, добавьте новую.");
            }
            None => {}
        }
        self.reward_coins.insert(&coin.token, &coin);

        log!(
            "{} добавил RewardCoin=> {:?}",
            env::predecessor_account_id(),
            coin
        )
    }

Листинг 2.24: internal_add_reward_coin:pool.rs

Влияние Количество доступных для добавления вознаграждаемых монет противоречит дизайну системы.

Предложение I Измените assert на self.reward_coins.len() < REWARD_UPPER_BOUND.

2.2.16 Потенциальная проблема 20: Пользователи с разными привилегиями используют один и тот же белый список

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функции assert_param_white, assert_white, assert_esm_white, assert_oracle_white используются для различных привилегий. Однако они делят один и тот же белый список.

pub(crate) fn assert_esm_white(&self) {
        self.assert_white()
    }

Листинг 2.25: assert_esm_white:esm.rs

pub(crate) fn assert_param_white(&self) {
        self.assert_white();
    }

Листинг 2.26: assert_param_white:dparam.rs

pub(crate) fn assert_oracle_white(&self) {
        self.assert_white();
    }

Листинг 2.27: assert_oracle_white:oracle.rs

Влияние Пользователи с разными привилегиями используют один и тот же белый список.

Предложение I Реализуйте разные белые списки для пользователей с разными привилегиями.

2.2.17 Потенциальная проблема 21: burn_coin не проверяет тип токена

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция burn_coin не проверяет тип токена. В этом случае злоумышленники могут передавать произвольные токены в указанном количестве для оплаты стабильной комиссии.

pub fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        assert!(self.is_redeem_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(sender_id);

Листинг 2.28: assert_esm_white:esm.rs

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

Предложение I Проверяйте адрес полученного токена.

2.2.18 Потенциальная проблема 22: Параметр total_reward вознаграждаемой монеты может быть изменен мультиподписными менеджерами

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Функция inject_reward декорирована #[private]. Следовательно, менеджеры мультиподписей могут вызывать эту функцию через запросы мультиподписи и добавлять произвольную сумму к общему вознаграждению без реального внесения средств.

#[payable]
    #[private]
    pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Сумма не равна transfer_amount"
            );
        }

        if let Some(reward_coin_ins) = self.get_reward_coin(reward_coin.clone()) {
            let mut reward_coin_ins = reward_coin_ins;
            reward_coin_ins.total_reward = reward_coin_ins
                .total_reward
                .checked_add(amount.into())
                .expect(ERR_SUB);
            self.reward_coins.insert(&reward_coin, &reward_coin_ins);

            if reward_coin == String::from("NEAR") {
            
            } else {
                log!("Перевод не требуется для пост-обработки");
            }
        } else {
            env::panic(b"Нет такой вознаграждаемой монеты.");
        }
    }

Листинг 2.29: ainject_reward:pool.rs

Предложение I Удалите декоратор #[private] и измените видимость функции inject_reward на приватную (внутреннюю).

2.3 Дополнительные рекомендации

2.3.1 Избыточное утверждение (assertion)

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-2. Функция inject_reward должна вызываться только внутренне через ft_on_transfer. Адрес вознаграждаемой монеты проверяется в ft_on_transfer. В этом случае нам не нужно проверять имя вознаграждаемой монеты в начале функции inject_reward.

#[payable]
    #[private]
    pub  fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Сумма не равна transfer_amount"
            );
        }

    ...
    }

Листинг 2.30: inject_reward:pool.rs


    pub fn ft_on_transfer(
        &mut self,
        sender_id: ValidAccountId,
        amount: U128,
        msg: String, /* токен */
    ) -> PromiseOrValue<U128> {
    ...
            FtOnTransferArgs::InjectReward => {
                assert_eq!(sender_id.to_string(), self.owner_id, "ERR_NOT_ALLOWED");

                assert!(
                    self.reward_coins.get(&token_account_id).is_some(),
                    "Недопустимая вознаграждаемая монета"
                );

                self.inject_reward(amount, token_account_id);
                amount_return = 0;
            }
    ...
    }

Листинг 2.31: ft_on_transfer:lib.rs

Предложение I Удалите проверку имени вознаграждаемой монеты в inject_reward.

2.3.2 Повторное утверждение для коэффициента ликвидации пользователя

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Линия ликвидации уже учитывается в функции internal_avaliable_token, поэтому нет необходимости проверять, достигает ли user_ratio линии ликвидации впоследствии.

#[payable]
    pub fn withdraw_token(&mut self, amount: U128) {
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let mut amount = amount.0;

        let token = self.internal_avaliable_token(env::predecessor_account_id());
        let debt = self.get_dept(env::predecessor_account_id());

        log!("токен :{} сумма: {}", token, amount);
        assert!(token >= amount, "Недостаточно доступных токенов.");
        if debt.0 - debt.2 == 0 {
            if token - amount < self._min_amount_token() {
                amount = token;
            }
        } else {
            self.assert_user_ratio();
            if token - amount < self._min_amount_token() {
                env::panic(b"Пожалуйста, сначала верните все монеты");
            }
        }

Листинг 2.32: withdraw_token:lib.rs

Предложение I Удалите лишнее утверждение в строке 559 Листинга 2.32.

2.3.3 Избыточная проверка белого списка

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция set_reward_speed вызывает функцию assert_param_white для проверки привилегий. В то же время internal_set_reward_speed, вызываемая из set_reward_speed, снова вызывает assert_white. У assert_white тот же белый список, что и у assert_param_white.

pub fn set_reward_speed(&mut self, reward_coin: AccountId, speed: U128) {
        self.assert_param_white();
        self.internal_set_reward_speed(reward_coin, speed);
    }

Листинг 2.33: set_reward_speed:dparam.rs

pub(crate) fn internal_set_reward_speed(&mut self, reward_coin: AccountId, speed: U128) {
        self.assert_white();
        self.update_index();
        . . .
    }

Листинг 2.34: internal_set_reward_speed:pool.rs

Предложение I Удалите assert_white внутри функции internal_set_reward_speed.

2.3.4 Неиспользуемая функция

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Функция on_inject_reward не используется никакими другими функциями. Таким образом, она может быть удалена.

#[private]
    pub fn on_inject_reward(&mut self, reward_coin: AccountId, amount: U128) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {}
            PromiseResult::Failed => {
                let mut reward_coin_ins = self.internal_get_reward_coin(reward_coin.clone());
                reward_coin_ins.total_reward = reward_coin_ins
                    .total_reward
                    .checked_sub(amount.into())
                    .expect(ERR_ADD);
                self.reward_coins.insert(&reward_coin, &reward_coin_ins);
            }
        };
    }

Листинг 2.35: on_inject_reward:pool.rs

Предложение I Удалите функцию on_inject_reward.

2.3.5 Избыточный код

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Функция account_allot.get() используется для получения выделенного вознаграждения и долга. Внутри функции set_account_allot вызов этой функции не требуется.

pub(crate) fn set_account_allot(&mut self,account_id: AccountId){
        //Обновить [персонально назначенный долг, персонально назначенный залог] до системного значения
        let (allot_debt, allot_token) = self.get_account_allot(account_id.clone());
        let token = self.account_token.get(&account_id).expect(ERR_NOT_REGISTER);
        let coin = self.account_coin.get(&account_id).expect(ERR_NOT_REGISTER);

        self.account_allot.get(&account_id);

        self.account_allot.insert(
            &account_id, 
            &AccountAllot{
                account_allot_debt: self.sys_allot_debt,
                account_allot_token: self.sys_allot_token,
            }
        );
        self.account_coin.insert(&account_id, &coin.checked_add(allot_debt).expect(ERR_ADD));
        self.account_token.insert(&account_id, &token.checked_add(allot_token).expect(ERR_ADD));       
    }

Листинг 2.36: set_account_allot:allot.rs

Предложение I Удалите вызов account_allot.get() в строке 42.

2.3.6 Функция называется противоположно своей реализации

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Функции is_stake_paused, is_redeem_paused, is_claim_reward_paused, is_liquidation_paused, is_stable_paused определены для представления того, приостановлена ли функция. Однако, когда соответствующий атрибут активен (live), возвращается True.

// TODO [OK]
    pub(crate) fn is_stake_paused(&self) -> bool {
        self.stake_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_redeem_paused(&self) -> bool {
        self.redeem_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_claim_reward_paused(&self) -> bool {
        self.claim_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_liquidation_paused(&self) -> bool {
        self.liquidation_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_stable_paused(&self) -> bool {
        self.stable_live == 1
    }

Листинг 2.37: is_{stake|redeem|claim_reward|liquidation|stable}_paused:esm.rs

Предложение I Измените название функций из is_{stake|redeem|claim_reward|liquidation|stable}paused на is{stake|redeem|claim_reward|liquidation|stable}_live.

2.3.7 Избыточный код

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Функция update_stable_fee используется для обновления требуемых стейбл-комиссий. Стейбл-комиссии не связаны со стейкинговыми токенами. Таким образом, изменение баланса токенов пользователей не требует обновления комиссий.

pub(crate) fn deposit_token(&mut self, _amount: u128, _sender_id: ValidAccountId) {
        self.assert_is_poked();
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(_sender_id);
        assert!(_amount > 0, "Сумма депонирования должна быть больше нуля.");

        if let Some(0) = self.guarantee.get(&sender_id) {
            assert!(
                _amount >= self._min_amount_token(),
                "Сумма депонирования должна быть больше минимально допустимой."
            );
        }
        self.update_personal_token(sender_id.clone());
        self.update_stable_fee(sender_id.clone());
        self.set_account_allot(sender_id.clone());
        . . .
    }

Листинг 2.38: deposit_token:lib.rs

Предложение I Удалите вызов update_stable_fee в строке 344.

2.3.8 Точность вычислений может быть улучшена

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-3. Функция internal_user_stable направлена на расчет стейбл-комиссии. Точность вычислений может быть улучшена за счет умножения перед делением.

pub(crate) fn update_stable_fee(&mut self, account: AccountId) {
        if let Some(mut user_stable) = self.account_stable.get(&account) {
            let allot = self.get_account_allot(account.clone());
            let debt = allot.0;
            let current_block_number = self.to_nano( env::block_timestamp()) as u128;

            let coin = self.account_coin.get(&account).expect(ERR_NOT_REGISTER).checked_add(debt).expect(ERR_ADD);
            let delta_block = current_block_number.checked_sub(user_stable.block).expect(ERR_SUB);
            if delta_block > 0 && coin > 0 {
                let fee = self.stable_fee_rate//16
                        .checked_mul(delta_block).expect(ERR_MUL)
                        .checked_mul(coin).expect(ERR_MUL)//8
                        .checked_div(BLOCK_PER_YEAR).expect(ERR_DIV)
                        .checked_div(self.oin_price).expect(ERR_DIV)//8
                        .checked_div(ONE_COIN).expect(ERR_DIV);//8
                        
                self.saved_stable = self.saved_stable
                        .checked_add(fee).expect(ERR_ADD);

                user_stable.saved_stable = user_stable.saved_stable
                        .checked_add(fee).expect(ERR_ADD); 
            }
            
            user_stable.block = current_block_number;
            self.account_stable.insert(&account, &user_stable);    
            log!("Текущая комиссия стабилизации: {:?}",self.account_stable.get(&account));
        } else {
            env::panic(b"Не зарегистрирован")
        }
    }

Листинг 2.39: update_stable_fee:stablefee.rs

Предложение I Выполняйте умножение до деления в расчетах с 25 по 30 строку.

2.3.9 Система может не записывать ранее полученную цену

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-1. Функция реализована некорректно. Система может не записывать полученную (poked) цену, так как количество общих депонированных токенов в контракте в большинстве случаев больше 0.

pub fn poke(&mut self, token_price: U128) {
    ...
       if self.total_token > 0 {
           if self.internal_sys_ratio() <= INIT_MIN_RATIO_LINE {
                self.internal_shutdown();
           }
       }else {
            log!(
                "{} получил цену {} успешно.",
                env::predecessor_account_id(),
                token_price.0
            );
        }
    }

Листинг 2.40: poke:oracle.rs

Предложение I Запись операции получения цены токена не должна зависеть от количества депонированных токенов в контракте.

2.3.10 Разрывное распределение токенов обеспечения при ликвидации

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-4. Когда коэффициент стейкинга пользователя больше или равен 108.5%, пользователи должны платить liquidation_fee, которая составляет 2% от allot_debt. Однако, если коэффициент стейкинга пользователя меньше 108.5%, он/она не должен платить комиссию за ликвидацию. Это приводит к тому, что пользователь с более высоким коэффициентом стейкинга может передать в пул меньше стейкинговых токенов после ликвидации.

#[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        ...
        if ratio >= INIT_NO_LIQUIDATION_FEE_RATE {
            liquidation_fee = _allot_debt
                            .checked_mul(self.liquidation_fee_ratio).expect(ERR_MUL)
                            .checked_mul(STAKE_RATIO_BASE).expect(ERR_MUL)//16
                            .checked_div(self.token_price).expect(ERR_DIV);
        }else{
            allot_ratio = ratio
                .checked_sub(self.gas_compensation_ratio).expect(ERR_SUB)
                .checked_add(1).expect(ERR_ADD);
        }
        ...

Листинг 2.41: liquidation:lib.rs

Предложение I Для пользователя, чей коэффициент стейкинга составляет от 108.5% до 110.5%, комиссию за ликвидацию предлагается установить равной (коэффициент стейкинга - 108.5%).

2.3.11 Оптимизация точности вычислений не требуется

Элемент Описание
Статус Подтверждено и исправлено

Описание Эта проблема появилась не позднее коммита-4. Добавление 1 в строке 832 в листинге 2.42 не может увеличить точность вычислений, так как self.gas_compensation_ratio довольно велик.

#[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        ...
        if ratio >= INIT_NO_LIQUIDATION_FEE_RATE {
            liquidation_fee = _allot_debt
                            .checked_mul(self.liquidation_fee_ratio).expect(ERR_MUL)
                            .checked_mul(STAKE_RATIO_BASE).expect(ERR_MUL)//16
                            .checked_div(self.token_price).expect(ERR_DIV);
        }else{
            allot_ratio = ratio
                .checked_sub(self.gas_compensation_ratio).expect(ERR_SUB)
                .checked_add(1).expect(ERR_ADD);
        }
        ...

Листинг 2.42: liquidation:lib.rs

Предложение I Удалите добавленную "1" в строке 831 Листинга 2.42.

2.3.12 Риск централизованного дизайна

Статус Принято

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

3. Уведомления и примечания

3.1 Отказ от ответственности

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

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

Объем этого аудита ограничен кодом, упомянутым в Разделе 1.1. Если явно не указано иное, безопасность самого языка (например, языка Rust), используемой инструментальной среды компиляции и вычислительной инфраструктуры выходит за рамки аудита.

3.2 Процедура аудита

Мы проводим аудит в соответствии со следующей процедурой.

  • Обнаружение уязвимостей Сначала мы сканируем смарт-контракты с помощью автоматических анализаторов кода, а затем вручную проверяем (отклоняем или подтверждаем) проблемы, сообщенные ими.

  • Семантический анализ Мы изучаем бизнес-логику смарт-контрактов и проводим дальнейшее исследование возможных уязвимостей с помощью автоматического инструмента фаззинга (разработанного нашей исследовательской группой). Мы также вручную анализируем возможные сценарии атак с участием независимых аудиторов для перекрестной проверки результатов.

  • Рекомендации Мы даем полезные советы разработчикам с точки зрения передовых методов программирования, включая оптимизацию газа, стиль написания кода и т.д.

Основные контрольные точки приведены ниже.

3.2.1 Программная безопасность

  • Реентрантность (Reentrancy)

  • DoS

  • Контроль доступа

  • Обработка данных и поток данных

  • Обработка исключений

  • Недоверенный внешний вызов и поток управления

  • Согласованность инициализации

  • Работа с событиями

  • Ошибочная случайность

  • Неправильное использование прокси-системы

3.2.2 Безопасность DeFi

  • Семантическая согласованность

  • Согласованность функциональности

  • Контроль доступа

  • Бизнес-логика

  • Операции с токенами

  • Механизм аварийной ситуации

  • Безопасность оракула

  • Белые и черные списки

  • Экономическое воздействие

  • Пакетная передача

3.2.3 Безопасность NFT

  • Дублирование элементов

  • Верификация получателя токена

  • Безопасность метаданных вне сети (off-chain)

3.2.4 Дополнительные рекомендации

  • Оптимизация газа

  • Качество и стиль кода

Приведенные выше контрольные точки являются основными. Мы можем использовать дополнительные контрольные точки в процессе аудита в зависимости от функциональности проекта.

Sign up for the latest updates
За пределами смарт-контракта: операционная безопасность доменов и DNS в Web3
Security Insights

За пределами смарт-контракта: операционная безопасность доменов и DNS в Web3

Аудит контрактов не проверяет сам контракт. Мы провели 800 SEAL-проверок DNS и регистраторов для 100 доменов из TVL Top 100 DefiLlama — лишь один домен прошёл все. Каких четырёх мер защиты не хватает большинству и почему это критично для пользователя.

Поверхности атаки Web3: обзор тестирования на проникновение

Поверхности атаки Web3: обзор тестирования на проникновение

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

Потеряно ~23 млн $: эксплойты Cosmos EVM и Moonwell | BlockSec Weekly
Security Insights

Потеряно ~23 млн $: эксплойты Cosmos EVM и Moonwell | BlockSec Weekly

За период (22.08.2026–30.08.2026): 5 инцидентов, ~$22,7 млн убытков; Tectonic — $74–119,5 млн, откачены на Cronos. Взлом Cosmos EVM (~$5,7 млн) на TAC Chain, Moonwell, Ajna, Rain Card Exploit (Ed25519) на Solana.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit
Отчет об аудите безопасности NearOinDao