В комментариях спросили про деплой смарт контрактов мультисиг кошельком. Хорошая тема, которую мы не разбирали на канале. Я хочу собрать интересный материал на эту тему и в ближайшее время сделать несколько постов. А пока, давайте поговорим про интеграцию с Chainlink.
Очень часто в конкурсных аудитах проскальзывают репорты с какой-либо уязвимостью при использовании Chainlink сервисов, поэтому хотел бы рассказать о некоторых моментах, на которые стоит обращать внимание.
В документации Chainlink есть руководство по интеграции, включающее образец контракта. К сожалению, он недостаточен для большинства случаев использования.
Мы рассмотрим три основных нюанса безопасности, которые необходимо учитывать каждый раз, когда вы интегрируете Chainlink в свой проект.
Вот официальный образец контракта (отредактированный для краткости):
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract DegenDeFi {
AggregatorV3Interface internal constant dataFeed =
AggregatorV3Interface(
0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
);
function getLatestData() public view returns (int256) {
(
/* uint80 roundId */,
int256 answer,
/* uint256 startedAt */,
/* uint256 updatedAt */,
/* uint80 answeredInRound */
) = dataFeed.latestRoundData();
return answer;
}
}
Он вызывает функцию latestRoundData() на dataFeed и возвращает ответ int256. Внимательные читатели заметят, что ответ технически может быть отрицательным.
Отрицательные цены активов не так часто встречаются, при этом они не невозможны: на фьючерсных рынках иногда наблюдается падение котировок ниже 0 при шоке спроса и предложения.
Работа с отрицательными и нулевыми ответами
Поскольку большинство приложений DeFi предполагают неотрицательную цену, мы должны привести ответ к значению uint256 и вернуть его. Как реагировать на ответ <= 0, зависит от ситуации.
Подсказка: Могут ли пользователи взаимодействовать с бесплатным активом (токеном), не испортив расчеты вашего протокола? Если да, то возвращайте 0 вместо отрицательных значений. Если нет, то лучше вернуться и дождаться корректного ответа от dataFeed.
function getLatestData() public view returns (uint256) {
(
/* uint80 roundId */,
int256 answer,
/* uint256 startedAt */,
/* uint256 updatedAt */,
/* uint80 answeredInRound */
) = dataFeed.latestRoundData();
// ✨ added sanity check
if (answer <= 0) {
// Option 1 (Recommended): Assume something went wrong
revert InvalidAnswer(answer);
// Option 2: Treat it as a 0
// return 0;
}
return uint256(answer);
}
Добавьте проверку на устаревшие данные
У Chainlink Feeds есть два параметра триггера, которые определяют, когда ответ должен быть обновлен. Они называются порог отклонения и сердцебиение - deviation threshold и heartbeat.
В приведенном выше на скрине примере, цены будут обновляться либо:
1. когда цена вне цепи изменяется более чем на ±0,5% по сравнению с последней опубликованной ценой, либо
2. если с момента последнего обновления прошло 24 часа.
Несмотря на эти правила, интеграторы не должны полагать, что сообщаемая цена находится в пределах 0,5% от цены off-chain или что она не более чем на 24 часа устарела.
Это связано с тем, что для записи обновления в этот feed требуется, чтобы на него ответили как минимум 11 из 16 поставщиков данных. Если кворум не соблюдается, максимальная застойность и отклонение последнего ответа feed теоретически неограниченны.
