Отчет о манифесте
| Элемент | Описание |
|---|---|
| Клиент | 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 Дополнительные рекомендации
-
Оптимизация газа
-
Качество и стиль кода
Приведенные выше контрольные точки являются основными. Мы можем использовать дополнительные контрольные точки в процессе аудита в зависимости от функциональности проекта.



