Back to Blog

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

Code Auditing
December 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 Дополнительные рекомендации

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

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

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

Best Security Auditor for Web3

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

BlockSec Audit