Насколько хороши аудиторские отчеты?
Вы когда-ниодь задумывались о том, насколько развернутыми и качественными бывают аудиторские отчеты для смарт-контрактов? За последние четыре месяца я собрал и проанализировал базу из более чем двадцати четырех тысяч уязвимостей, извлеченных из отчетов частных аудиторов, компаний и конкурсных платформ. Эта работа стала фундаментом для создания интеллектуального ассистента, призванного помогать в аудите. Ключевым этапом была оценка информационной ценности каждого отдельного отчета.
Для этого была разработана система взвешивания. Основной вклад вносили три компонента: детальное описание проблемы, рекомендации по ее устранению и наличие доказательства концепции (PoC). Наибольший вес был назначен доказательству концепции, так как его наличие существенно повышает практическую ценность отчета. Дополнительные баллы начислялись за включение фрагментов кода на Solidity и за уровень серьезности самой уязвимости.
Для того, чтобы система оценок была более справедливой и сбалансированной, сырые баллы прошли процесс нормализации. Была применена логарифмическая функция, которая сжимает исходный диапазон значений от нуля до пятнадцати в интервал от нуля до единицы. Это предотвратило ситуацию, при которой несколько идеальных отчетов с максимальными баллами доминировали бы над всеми остальными, и позволило выстроить более плавный и информативный градиент качества.
Статистический анализ полученных данных выявил любопытные закономерности. Средний вес по всем отчетам составил 0.32 при медианном значении 0.27. Это означает, что половина всех проанализированных отчетов имеет оценку ниже 0.27. Распределение весов является скошенным, с явным пиком в области низких значений, что указывает на преобладание относительно кратких или неполных описаний уязвимостей.
Качественными можно считать лишь около четверти всех отчетов, чей вес превышает значение 0.51. Наличие доказательства концепции и сопровождающего его кода является ключевым фактором, который отделяет эту группу от общей массы. Именно эти отчеты формируют так называемый золотой фонд данных, наиболее пригодный для эффективного обучения моделей искусственного интеллекта.
Проведенная работа наглядно демонстрирует, что подробные отчеты с практическими примерами реализации уязвимости пока что являются скорее исключением, чем правилом. Это создает определенные вызовы для автоматизации аудита, но одновременно выделяет истинную ценность глубоких и тщательно проработанных исследований. Собранный датасет с взвешенными уязвимостями открывает путь к созданию инструмента, способного существенно повысить эффективность и точность анализа безопасности смарт-контрактов.
Такой анализ подчеркивает важность не только обнаружения уязвимости, но и качества ее документации для всего сообщества. Полнота отчета напрямую влияет на скорость и точность его обработки как человеком, так и алгоритмом, что в конечном счете способствует повышению общего уровня безопасности в экосистеме блокчейна.
#notsosmart
Вы когда-ниодь задумывались о том, насколько развернутыми и качественными бывают аудиторские отчеты для смарт-контрактов? За последние четыре месяца я собрал и проанализировал базу из более чем двадцати четырех тысяч уязвимостей, извлеченных из отчетов частных аудиторов, компаний и конкурсных платформ. Эта работа стала фундаментом для создания интеллектуального ассистента, призванного помогать в аудите. Ключевым этапом была оценка информационной ценности каждого отдельного отчета.
Для этого была разработана система взвешивания. Основной вклад вносили три компонента: детальное описание проблемы, рекомендации по ее устранению и наличие доказательства концепции (PoC). Наибольший вес был назначен доказательству концепции, так как его наличие существенно повышает практическую ценность отчета. Дополнительные баллы начислялись за включение фрагментов кода на Solidity и за уровень серьезности самой уязвимости.
Для того, чтобы система оценок была более справедливой и сбалансированной, сырые баллы прошли процесс нормализации. Была применена логарифмическая функция, которая сжимает исходный диапазон значений от нуля до пятнадцати в интервал от нуля до единицы. Это предотвратило ситуацию, при которой несколько идеальных отчетов с максимальными баллами доминировали бы над всеми остальными, и позволило выстроить более плавный и информативный градиент качества.
Статистический анализ полученных данных выявил любопытные закономерности. Средний вес по всем отчетам составил 0.32 при медианном значении 0.27. Это означает, что половина всех проанализированных отчетов имеет оценку ниже 0.27. Распределение весов является скошенным, с явным пиком в области низких значений, что указывает на преобладание относительно кратких или неполных описаний уязвимостей.
Качественными можно считать лишь около четверти всех отчетов, чей вес превышает значение 0.51. Наличие доказательства концепции и сопровождающего его кода является ключевым фактором, который отделяет эту группу от общей массы. Именно эти отчеты формируют так называемый золотой фонд данных, наиболее пригодный для эффективного обучения моделей искусственного интеллекта.
Проведенная работа наглядно демонстрирует, что подробные отчеты с практическими примерами реализации уязвимости пока что являются скорее исключением, чем правилом. Это создает определенные вызовы для автоматизации аудита, но одновременно выделяет истинную ценность глубоких и тщательно проработанных исследований. Собранный датасет с взвешенными уязвимостями открывает путь к созданию инструмента, способного существенно повысить эффективность и точность анализа безопасности смарт-контрактов.
Такой анализ подчеркивает важность не только обнаружения уязвимости, но и качества ее документации для всего сообщества. Полнота отчета напрямую влияет на скорость и точность его обработки как человеком, так и алгоритмом, что в конечном счете способствует повышению общего уровня безопасности в экосистеме блокчейна.
#notsosmart
🔥9
Replay атака. Часть 1
Атака повторного воспроизведения происходит, когда действительная передача данных, такая как подписанное сообщение или транзакция, злонамеренно повторяется. В смарт контрактах злоумышленник перехватывает законное, подписанное действие пользователя. Затем он «воспроизводит» его позже в другой сети, чтобы вызвать непреднамеренное изменение состояния, такое как списание средств или выполнение разрешенной функции без авторизации.
Для защиты от таких атак разработчики используют несколько важных инструментов:
1. Nonce («одноразовое число»): в блокчейне nonce — это уникальный последовательный счетчик, привязанный к адресу пользователя. Требуя, чтобы каждое подписанное действие включало текущее nonce пользователя, контракт может гарантировать, что каждое действие будет выполнено только один раз. После обработки nonce увеличивается, поэтому подпись не может быть использована повторно.
2. Параметры, специфичные для цепочки: с появлением нескольких блокчейнов, совместимых с Ethereum Virtual Machine (EVM) (например, Ethereum, Polygon, Arbitrum и т. д.), приватный ключ и адрес пользователя часто одинаковы во всех сетях. Подпись, созданная для контракта на Ethereum, может быть действительна для идентичного контракта на Polygon, если она не содержит данных, специфичных для этой сети. Включение block.chainid в подписанные данные связывает подпись с одной конкретной сетью, предотвращая межсетевые атаки повторного воспроизведения.
3. Разделитель домена: это криптографический механизм, стандартизированный в EIP-712, который связывает подпись с конкретным контекстом приложения. Это уникальный хеш, содержащий такую информацию, как название контракта, версия, адрес и chainid. Это гарантирует, что подпись, предназначенная для одного децентрализованного приложения (DApp), не может быть повторно использована в другом, обеспечивая надежную защиту как от межсетевых, так и от cross-DApp атак повторного воспроизведения.
Теперь давайте рассмотрим, как применять эти механизмы к конкретным уязвимостям.
Существуют ли меры защиты от атак повторного воспроизведения для неудачных транзакций?
Эта проверка сосредоточена на распространенном недостатке реализации: увеличение nonce пользователя только после успешного завершения транзакции. Если транзакция завершается неудачно из-за временных обстоятельств (например, в контракте недостаточно средств), nonce остается неизменным. Подписанное сообщение по-прежнему остается действительным и может быть повторно использовано любым лицом после устранения обстоятельств.
Рассмотрим контракт RewardSystem, который позволяет пользователям получать заработанные вознаграждения.
Атака повторного воспроизведения происходит, когда действительная передача данных, такая как подписанное сообщение или транзакция, злонамеренно повторяется. В смарт контрактах злоумышленник перехватывает законное, подписанное действие пользователя. Затем он «воспроизводит» его позже в другой сети, чтобы вызвать непреднамеренное изменение состояния, такое как списание средств или выполнение разрешенной функции без авторизации.
Для защиты от таких атак разработчики используют несколько важных инструментов:
1. Nonce («одноразовое число»): в блокчейне nonce — это уникальный последовательный счетчик, привязанный к адресу пользователя. Требуя, чтобы каждое подписанное действие включало текущее nonce пользователя, контракт может гарантировать, что каждое действие будет выполнено только один раз. После обработки nonce увеличивается, поэтому подпись не может быть использована повторно.
2. Параметры, специфичные для цепочки: с появлением нескольких блокчейнов, совместимых с Ethereum Virtual Machine (EVM) (например, Ethereum, Polygon, Arbitrum и т. д.), приватный ключ и адрес пользователя часто одинаковы во всех сетях. Подпись, созданная для контракта на Ethereum, может быть действительна для идентичного контракта на Polygon, если она не содержит данных, специфичных для этой сети. Включение block.chainid в подписанные данные связывает подпись с одной конкретной сетью, предотвращая межсетевые атаки повторного воспроизведения.
3. Разделитель домена: это криптографический механизм, стандартизированный в EIP-712, который связывает подпись с конкретным контекстом приложения. Это уникальный хеш, содержащий такую информацию, как название контракта, версия, адрес и chainid. Это гарантирует, что подпись, предназначенная для одного децентрализованного приложения (DApp), не может быть повторно использована в другом, обеспечивая надежную защиту как от межсетевых, так и от cross-DApp атак повторного воспроизведения.
Теперь давайте рассмотрим, как применять эти механизмы к конкретным уязвимостям.
Существуют ли меры защиты от атак повторного воспроизведения для неудачных транзакций?
Эта проверка сосредоточена на распространенном недостатке реализации: увеличение nonce пользователя только после успешного завершения транзакции. Если транзакция завершается неудачно из-за временных обстоятельств (например, в контракте недостаточно средств), nonce остается неизменным. Подписанное сообщение по-прежнему остается действительным и может быть повторно использовано любым лицом после устранения обстоятельств.
Рассмотрим контракт RewardSystem, который позволяет пользователям получать заработанные вознаграждения.
❤3
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/access/Ownable.sol";
contract RewardSystem is Ownable {
mapping(address => uint256) public rewards;
mapping(address => uint256) public nonces;
constructor() Ownable(msg.sender) {}
// For PoC simplicity, we don't use real signatures
function claimReward(address user, uint256 amount, uint256 nonce, bytes memory signature) external {
require(rewards[user] >= amount, "Insufficient reward balance");
require(nonces[user] == nonce, "Invalid nonce");
// Vulnerability: Signature can be replayed once contract has funds
bytes32 messageHash = keccak256(abi.encode(
user,
amount,
nonce
));
// For PoC, use a simple signature check
bytes32 signedHash = abi.decode(signature, (bytes32));
require(signedHash == messageHash, "Invalid signature");
// Attempt to transfer reward
rewards[user] -= amount;
(bool success,) = msg.sender.call{value: amount}("");
// Vulnerability: Nonce is only incremented if transfer succeeds
if (success) {
nonces[user]++;
} else {
revert("Transfer failed");
}
}
// Helper function to add rewards - only owner can call
function addReward(address user, uint256 amount) external onlyOwner {
rewards[user] += amount;
}
// Helper function to receive ETH
receive() external payable {}
}
Функция claimReward увеличивает значение nonce пользователя только в случае успешного перевода ETH. Если в контракте нет ETH, msg.sender.call завершится с ошибкой, транзакция будет отменена, а значение nonces[user] не будет увеличено. Подпись пользователя, которая была действительна для этого значения nonce, остается действительной.
Злоумышленник может отслеживать эти неудачные заявки. Как только контракт RewardSystem будет профинансирован, злоумышленник может повторить исходную транзакцию пользователя, направив вознаграждение на свой собственный адрес.
Как исправить: используйте nonce в каждом пути выполнения!
Убедитесь, что nonce используется (помечается как использованный) в каждом пути выполнения, независимо от того, завершилось ли оно успешно или неудачно. Лучшая практика заключается в том, чтобы использовать nonce сразу после его проверки и убедиться, что транзакция не будет отменена после этого момента. Таким образом, даже если транзакция завершится неудачно, nonce все равно будет увеличен, что предотвратит атаки повторного воспроизведения.
// Corrected claimReward function (inside RewardSystem)
function claimReward(address user, uint256 amount, uint256 nonce, bytes memory signature) external {
require(rewards[user] >= amount, "Insufficient reward balance");
require(nonces[user] == nonce, "Invalid nonce");
// For PoC, use a simple signature check
bytes32 messageHash = keccak256(abi.encode(user, amount, nonce));
bytes32 signedHash = abi.decode(signature, (bytes32));
require(signedHash == messageHash, "Invalid signature");
// REMEDIATION 1: Consume nonce right after validating it
nonces[user]++;
rewards[user] -= amount;
(bool success,) = msg.sender.call{value: amount}("");
if (!success) {
// Revert the reward deduction if transfer failed
rewards[user] += amount;
}
// REMEDIATION 2: No revert to ensure nonce is consumed
}
С этим исправлением, даже если передача не удалась, nonce становится недействительным. Злоумышленник не может повторно использовать подпись, поскольку контракт теперь будет ожидать nonce + 1.
#replay
👍2🔥1
Replay атака. Часть 2
Существует ли защита от повторного использования подписей в разных сетях?
Этот пункт проверки касается критической уязвимости нескольких сетей. Поскольку приватный ключ пользователя контролирует его адрес во всех сетях EVM, подпись, созданная для контракта на Ethereum, может быть повторно использована при идентичном развертывании этого контракта на Polygon, Arbitrum или любой другой сети EVM. Если подпись не содержит идентификатор блокчейна, она становится универсальным ключом, который злоумышленник может использовать в разных сетях.
Рассмотрим контракт VulnerableVault, предназначенный для того, чтобы его владелец мог изменить получателя с помощью подписанного сообщения.
MessageHash включает нового получателя и метку времени истечения срока действия, но в нем отсутствует block.chainid. Это означает, что подпись, сгенерированная для этого действия, является переносимой, т. е. злоумышленник может взять действительную подпись из одной сети и использовать ее в другой.
Исправление: встраивание данных, специфичных для цепочки, в подписи.
Прямым исправлением является включение block.chainid в данные, которые хэшируются и подписываются. Это навсегда привязывает подпись к одной сети. Для более надежного и стандартизированного решения используйте EIP-712, который встраивает эту защиту непосредственно в свою структуру через разделитель домена.
Вот простое исправление, примененное к новому контракту SecureVault.
Атаки с повторным воспроизведением используют действительные намерения пользователей в недействительных контекстах. Понимая, как злоумышленники могут дублировать действия во времени или между сетями, мы можем встроить в наши контракты более точную и безопасную логику проверки.
Разработка безопасных контрактов требует верной точки зрения:
Существует ли защита от повторного использования подписей в разных сетях?
Этот пункт проверки касается критической уязвимости нескольких сетей. Поскольку приватный ключ пользователя контролирует его адрес во всех сетях EVM, подпись, созданная для контракта на Ethereum, может быть повторно использована при идентичном развертывании этого контракта на Polygon, Arbitrum или любой другой сети EVM. Если подпись не содержит идентификатор блокчейна, она становится универсальным ключом, который злоумышленник может использовать в разных сетях.
Рассмотрим контракт VulnerableVault, предназначенный для того, чтобы его владелец мог изменить получателя с помощью подписанного сообщения.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract VulnerableVault {
address public owner;
address public recipient;
mapping(bytes32 => bool) public isConsumed;
constructor(address _owner, address _recipient) {
owner = _owner;
recipient = _recipient;
}
function changeRecipient(address _newRecipient, uint256 _expiry, bytes memory _signature) external {
require(block.timestamp <= _expiry, "Signature expired");
// VULNERABILITY: Missing chain ID in the signature data
bytes32 messageHash = keccak256(abi.encode(
msg.sender, // In the PoC, this is the test contract's address
_newRecipient,
_expiry
));
// For PoC, use a simple signature check
bytes32 signedHash = abi.decode(_signature, (bytes32));
require(signedHash == messageHash, "Invalid signature");
require(!isConsumed[messageHash], "Signature already used");
isConsumed[messageHash] = true;
recipient = _newRecipient;
}
}
MessageHash включает нового получателя и метку времени истечения срока действия, но в нем отсутствует block.chainid. Это означает, что подпись, сгенерированная для этого действия, является переносимой, т. е. злоумышленник может взять действительную подпись из одной сети и использовать ее в другой.
Исправление: встраивание данных, специфичных для цепочки, в подписи.
Прямым исправлением является включение block.chainid в данные, которые хэшируются и подписываются. Это навсегда привязывает подпись к одной сети. Для более надежного и стандартизированного решения используйте EIP-712, который встраивает эту защиту непосредственно в свою структуру через разделитель домена.
Вот простое исправление, примененное к новому контракту SecureVault.
// Corrected changeRecipient function (inside a new SecureVault contract)
contract SecureVault {
// ... same state variables and constructor as VulnerableVault ...
address public owner;
address public recipient;
mapping(bytes32 => bool) public isConsumed;
constructor(address _owner, address _recipient) {
owner = _owner;
recipient = _recipient;
}
function changeRecipient(address _newRecipient, uint256 _expiry, bytes memory _signature) external {
require(block.timestamp <= _expiry, "Signature expired");
// REMEDIATION: Include the chain ID to make the signature chain-specific.
bytes32 messageHash = keccak256(abi.encode(
msg.sender,
_newRecipient,
_expiry,
block.chainid // Added chain ID
));
// For PoC, use a simple signature check
bytes32 signedHash = abi.decode(_signature, (bytes32));
require(signedHash == messageHash, "Invalid signature");
require(!isConsumed[messageHash], "Signature already used");
isConsumed[messageHash] = true;
recipient = _newRecipient;
}
}
Атаки с повторным воспроизведением используют действительные намерения пользователей в недействительных контекстах. Понимая, как злоумышленники могут дублировать действия во времени или между сетями, мы можем встроить в наши контракты более точную и безопасную логику проверки.
Разработка безопасных контрактов требует верной точки зрения:
🔥1
- Всегда спрашивайте: полностью ли я аннулировал эту подпись после использования? Nonce должен быть использован независимо от того, удалась ли транзакция или нет.
- Проверяйте контекст подписи: где эта подпись действительна? Если ваш протокол работает или может работать в нескольких сетях, каждая внесетевая подпись должна быть привязана к конкретной сети через block.chainid или разделитель домена EIP-712.
- Проверяйте каждое предположение: предполагает ли мой код, что транзакция будет успешной? Предполагает ли он, что подпись предназначена только для одной сети? Проактивное выявление и устранение этих неявных предположений является ключом к предотвращению атак повторного воспроизведения.
Строго применяя эти принципы и используя такие инструменты, как чек-лист Solodit, вы можете создавать системы, устойчивые к этим тонким, но опасным уязвимостям.
#replay
- Проверяйте контекст подписи: где эта подпись действительна? Если ваш протокол работает или может работать в нескольких сетях, каждая внесетевая подпись должна быть привязана к конкретной сети через block.chainid или разделитель домена EIP-712.
- Проверяйте каждое предположение: предполагает ли мой код, что транзакция будет успешной? Предполагает ли он, что подпись предназначена только для одной сети? Проактивное выявление и устранение этих неявных предположений является ключом к предотвращению атак повторного воспроизведения.
Строго применяя эти принципы и используя такие инструменты, как чек-лист Solodit, вы можете создавать системы, устойчивые к этим тонким, но опасным уязвимостям.
#replay
👍3🔥1
Мое обучение за два месяца
С середины августа я начал погружаться в тему нейронных сетей — не ради сиюминутного результата, а чтобы по-настоящему понять, как устроены современные модели машинного обучения. С самого начала было ясно: чтобы разобраться в нейросетях глубоко, нужно пройти путь от основ математики до архитектур, которые сегодня решают реальные задачи. И вот уже больше двух месяцев я каждый день уделяю время этому пути.
Сначала пришлось вернуться к линейной алгебре — векторам, матрицам, операциям над ними, системам линейных уравнений. Без этого невозможно понять даже простейший перцептрон. Потом дошли до собственных значений и векторов, а также до матричных разложений вроде SVD и PCA — инструментов, которые лежат в основе многих методов понижения размерности и сжатия данных.
Параллельно я проходил теорию вероятностей: от базовых понятий вроде случайных событий и условной вероятности до более сложных тем — совместных распределений, теоремы Байеса и даже вводных понятий байесовских сетей. Особенно важными оказались такие концепции, как энтропия, KL-дивергенция и кросс-энтропия — они напрямую связаны с тем, как нейросети учатся и минимизируют ошибку. А закон больших чисел, казалось бы, абстрактный, на деле объясняет, почему стохастический градиентный спуск вообще работает.
Конечно, теория — это лишь часть дела. Пришлось осваивать и практические инструменты: NumPy для эффективной работы с массивами и векторизованными вычислениями, Pandas — для манипуляций с данными, фильтрации, группировки и объединения таблиц. Без этих библиотек невозможно представить себе ни один реальный проект в машинном обучении.
Постепенно я добрался и до самих нейронных сетей. Начал с искусственного нейрона и перцептрона, затем перешёл к многослойным сетям, функциям активации, прямому и обратному распространению ошибки. Изучил, как работают разные оптимизаторы — от классического SGD до более продвинутых, вроде Adam и RMSprop. Потом пришло время свёрточных нейросетей: разобрался с операциями свёртки и пулинга, изучил ключевые архитектуры — от LeNet и AlexNet до VGG, ResNet и DenseNet. Это открыло понимание того, как решаются задачи компьютерного зрения: классификация, детекция, сегментация.
Недавно начал знакомиться с RAG — подходом, сочетающим поиск по внешним источникам и генерацию текста с помощью языковых моделей, — а также с устройством векторных баз данных, которые лежат в основе таких систем.
И при всём этом я отчётливо понимаю: пройденный путь — лишь начало. То, что я планировал освоить за пару месяцев, скорее всего, займёт не меньше года. Но это не разочарование — наоборот, процесс увлекает всё больше. Каждая новая тема раскрывает связи между, казалось бы, разными областями знаний, и это делает обучение по-настоящему увлекательным.
Хочу подчеркнуть: я не забросил свою основную специализацию. Solidity и разработка смарт-контрактов остаются моей профессиональной основой. Просто сейчас я временно поставил эту работу на паузу, чтобы заложить прочный фундамент в области машинного обучения. В будущем, возможно, эти два направления даже пересекутся — ведь децентрализованные системы и ИИ уже начинают влиять друг на друга. А пока — продолжаю учиться, шаг за шагом.
P.S. Все это не составляет и трети от той программы, что я выкладывал ранее для обучения в этой сфере...
#ai
С середины августа я начал погружаться в тему нейронных сетей — не ради сиюминутного результата, а чтобы по-настоящему понять, как устроены современные модели машинного обучения. С самого начала было ясно: чтобы разобраться в нейросетях глубоко, нужно пройти путь от основ математики до архитектур, которые сегодня решают реальные задачи. И вот уже больше двух месяцев я каждый день уделяю время этому пути.
Сначала пришлось вернуться к линейной алгебре — векторам, матрицам, операциям над ними, системам линейных уравнений. Без этого невозможно понять даже простейший перцептрон. Потом дошли до собственных значений и векторов, а также до матричных разложений вроде SVD и PCA — инструментов, которые лежат в основе многих методов понижения размерности и сжатия данных.
Параллельно я проходил теорию вероятностей: от базовых понятий вроде случайных событий и условной вероятности до более сложных тем — совместных распределений, теоремы Байеса и даже вводных понятий байесовских сетей. Особенно важными оказались такие концепции, как энтропия, KL-дивергенция и кросс-энтропия — они напрямую связаны с тем, как нейросети учатся и минимизируют ошибку. А закон больших чисел, казалось бы, абстрактный, на деле объясняет, почему стохастический градиентный спуск вообще работает.
Конечно, теория — это лишь часть дела. Пришлось осваивать и практические инструменты: NumPy для эффективной работы с массивами и векторизованными вычислениями, Pandas — для манипуляций с данными, фильтрации, группировки и объединения таблиц. Без этих библиотек невозможно представить себе ни один реальный проект в машинном обучении.
Постепенно я добрался и до самих нейронных сетей. Начал с искусственного нейрона и перцептрона, затем перешёл к многослойным сетям, функциям активации, прямому и обратному распространению ошибки. Изучил, как работают разные оптимизаторы — от классического SGD до более продвинутых, вроде Adam и RMSprop. Потом пришло время свёрточных нейросетей: разобрался с операциями свёртки и пулинга, изучил ключевые архитектуры — от LeNet и AlexNet до VGG, ResNet и DenseNet. Это открыло понимание того, как решаются задачи компьютерного зрения: классификация, детекция, сегментация.
Недавно начал знакомиться с RAG — подходом, сочетающим поиск по внешним источникам и генерацию текста с помощью языковых моделей, — а также с устройством векторных баз данных, которые лежат в основе таких систем.
И при всём этом я отчётливо понимаю: пройденный путь — лишь начало. То, что я планировал освоить за пару месяцев, скорее всего, займёт не меньше года. Но это не разочарование — наоборот, процесс увлекает всё больше. Каждая новая тема раскрывает связи между, казалось бы, разными областями знаний, и это делает обучение по-настоящему увлекательным.
Хочу подчеркнуть: я не забросил свою основную специализацию. Solidity и разработка смарт-контрактов остаются моей профессиональной основой. Просто сейчас я временно поставил эту работу на паузу, чтобы заложить прочный фундамент в области машинного обучения. В будущем, возможно, эти два направления даже пересекутся — ведь децентрализованные системы и ИИ уже начинают влиять друг на друга. А пока — продолжаю учиться, шаг за шагом.
P.S. Все это не составляет и трети от той программы, что я выкладывал ранее для обучения в этой сфере...
#ai
👍14❤2🔥1
Развитие языка Solidity в скором будущем
Разработка Solidity входит в новую фазу. Долгое время команда сознательно ограничивала добавление новых функций в Classic Solidity, стремясь не усложнять его реализацию и не создавать архитектурных решений, которые могли бы помешать развитию Core Solidity — нового концепта языка. Язык находился в состоянии мягкой заморозки, поскольку основное внимание уделялось ускоренному созданию Core Solidity, где многие функции могли бы быть реализованы эффективнее — либо через стандартную библиотеку, либо как "синтаксический сахар" (буквальная цитата из статьи).
Однако по мере того как Core Solidity оформился в отдельный подпроект со своей собственной дорожной картой, подход к Classic Solidity был пересмотрен. Теперь планируется внедрение ограниченного набора новых языковых возможностей, которые не противоречат долгосрочной архитектуре Core Solidity. В ближайшее время будет опубликован список таких функций с оценкой их сложности, полезности и совместимости с будущим языком. Хотя не все из них успеют попасть в Classic Solidity до перехода на Core, многие из них считаются необходимыми для экосистемы в целом.
Ближайший релиз — версия 0.9 — не принесёт крупных новых возможностей. Его основная цель — упрощение и очистка кодовой базы. В частности, будет завершён переход на IR-конвейер, оптимизатор станет включённым по умолчанию, а поддержка устаревших функций, таких как .send(), .transfer() и виртуальные модификаторы, будет удалена. Также SMTChecker потеряет поддержку BMC и будет переименован в SolCMC. Эти шаги направлены на снижение технического долга и подготовку основы для будущих изменений.
В последующих версиях, начиная с 0.10, изменения будут ориентированы на сближение Classic Solidity с Core Solidity. Это включает постепенное внедрение нового синтаксиса: постфиксную запись типов, переработанный тернарный оператор, упрощённую нотацию функциональных типов, а также замену «магических» свойств обычными функциями. Параллельно будет заложена заглушка стандартной библиотеки, реализующая часть встроенных возможностей на уровне языка, с поддержкой пользовательских замен.
Особое внимание уделяется взаимодействию между Classic и Core Solidity. Планируется создать единый интерфейс компиляции, позволяющий использовать оба языка в одном проекте. На первом этапе контракты останутся независимыми, но их артефакты будут объединяться в общий выход компилятора. На втором этапе станет возможным импортировать и вызывать свободные функции, события, ошибки и типы между языками, что обеспечит плавный переход и гибкость при разработке.
Core Solidity представляет собой фундаментальное обновление системы типов Solidity с заимствованиями из функциональных и системных языков, таких как Haskell, Rust и Zig. Большинство высокоуровневых конструкций Classic Solidity станут частью стандартной библиотеки, а сам язык будет десахаризироваться в минималистичный внутренний язык SAIL. Он поддерживает параметрический полиморфизм, траиты, вывод типов, алгебраические типы и сопоставление с образцом — всё это создаёт основу для строгой формальной верификации и надёжной разработки.
Стандартная библиотека Core Solidity изначально будет минимальной, чтобы снизить нагрузку на компилятор и упростить его поддержку. Однако её дальнейшее развитие планируется осуществлять через открытый, управляемый сообществом процесс, аналогичный EIP. Это позволит расширять язык не через изменения компилятора, а через библиотечные решения, что делает экосистему более гибкой и адаптивной.
Наконец, Core Solidity создаётся с прицелом на формальную спецификацию. В отличие от Classic Solidity, который страдает от отсутствия чёткой семантики, новый язык достаточно компактен для полной формализации. Планируется предоставить исполняемую спецификацию, которая послужит эталонной реализацией и инструментом для верификации. Это также поможет избежать фрагментации: все компиляторы смогут следовать единой спецификации, конкурируя по качеству оптимизаций и реализации библиотек, а не по интерпретации языка.
#solidity
Разработка Solidity входит в новую фазу. Долгое время команда сознательно ограничивала добавление новых функций в Classic Solidity, стремясь не усложнять его реализацию и не создавать архитектурных решений, которые могли бы помешать развитию Core Solidity — нового концепта языка. Язык находился в состоянии мягкой заморозки, поскольку основное внимание уделялось ускоренному созданию Core Solidity, где многие функции могли бы быть реализованы эффективнее — либо через стандартную библиотеку, либо как "синтаксический сахар" (буквальная цитата из статьи).
Однако по мере того как Core Solidity оформился в отдельный подпроект со своей собственной дорожной картой, подход к Classic Solidity был пересмотрен. Теперь планируется внедрение ограниченного набора новых языковых возможностей, которые не противоречат долгосрочной архитектуре Core Solidity. В ближайшее время будет опубликован список таких функций с оценкой их сложности, полезности и совместимости с будущим языком. Хотя не все из них успеют попасть в Classic Solidity до перехода на Core, многие из них считаются необходимыми для экосистемы в целом.
Ближайший релиз — версия 0.9 — не принесёт крупных новых возможностей. Его основная цель — упрощение и очистка кодовой базы. В частности, будет завершён переход на IR-конвейер, оптимизатор станет включённым по умолчанию, а поддержка устаревших функций, таких как .send(), .transfer() и виртуальные модификаторы, будет удалена. Также SMTChecker потеряет поддержку BMC и будет переименован в SolCMC. Эти шаги направлены на снижение технического долга и подготовку основы для будущих изменений.
В последующих версиях, начиная с 0.10, изменения будут ориентированы на сближение Classic Solidity с Core Solidity. Это включает постепенное внедрение нового синтаксиса: постфиксную запись типов, переработанный тернарный оператор, упрощённую нотацию функциональных типов, а также замену «магических» свойств обычными функциями. Параллельно будет заложена заглушка стандартной библиотеки, реализующая часть встроенных возможностей на уровне языка, с поддержкой пользовательских замен.
Особое внимание уделяется взаимодействию между Classic и Core Solidity. Планируется создать единый интерфейс компиляции, позволяющий использовать оба языка в одном проекте. На первом этапе контракты останутся независимыми, но их артефакты будут объединяться в общий выход компилятора. На втором этапе станет возможным импортировать и вызывать свободные функции, события, ошибки и типы между языками, что обеспечит плавный переход и гибкость при разработке.
Core Solidity представляет собой фундаментальное обновление системы типов Solidity с заимствованиями из функциональных и системных языков, таких как Haskell, Rust и Zig. Большинство высокоуровневых конструкций Classic Solidity станут частью стандартной библиотеки, а сам язык будет десахаризироваться в минималистичный внутренний язык SAIL. Он поддерживает параметрический полиморфизм, траиты, вывод типов, алгебраические типы и сопоставление с образцом — всё это создаёт основу для строгой формальной верификации и надёжной разработки.
Стандартная библиотека Core Solidity изначально будет минимальной, чтобы снизить нагрузку на компилятор и упростить его поддержку. Однако её дальнейшее развитие планируется осуществлять через открытый, управляемый сообществом процесс, аналогичный EIP. Это позволит расширять язык не через изменения компилятора, а через библиотечные решения, что делает экосистему более гибкой и адаптивной.
Наконец, Core Solidity создаётся с прицелом на формальную спецификацию. В отличие от Classic Solidity, который страдает от отсутствия чёткой семантики, новый язык достаточно компактен для полной формализации. Планируется предоставить исполняемую спецификацию, которая послужит эталонной реализацией и инструментом для верификации. Это также поможет избежать фрагментации: все компиляторы смогут следовать единой спецификации, конкурируя по качеству оптимизаций и реализации библиотек, а не по интерпретации языка.
#solidity
👍13🔥1
OpenAI анонсировала Aardvark
Вчера OpenAI анонсировала Aardvark — автономного агентного исследователя безопасности, основанного на GPT‑5. Эта система призвана изменить подход к обнаружению и устранению уязвимостей в программном обеспечении, смещая баланс в пользу защитников. В условиях, когда ежегодно выявляется десятки тысяч новых уязвимостей, своевременное обнаружение и исправление критических ошибок становится вопросом не только качества кода, но и общей кибербезопасности инфраструктуры.
Aardvark представляет собой прорыв на стыке искусственного интеллекта и информационной безопасности. В отличие от традиционных методов анализа, таких как фаззинг или анализ состава программного обеспечения, Aardvark использует рассуждения на основе крупной языковой модели и взаимодействие с инструментами. Агент имитирует работу человека-исследователя: читает код, анализирует его поведение, пишет и запускает тесты, а также использует внешние утилиты для подтверждения своих выводов.
Процесс работы Aardvark построен на многоэтапном конвейере. На первом этапе система строит модель угроз для репозитория, отражая понимание его архитектуры и целей безопасности. Затем она отслеживает коммиты в реальном времени, сопоставляя изменения с общей моделью и выявляя потенциальные уязвимости. При первом подключении репозитория Aardvark также анализирует всю историю коммитов, чтобы обнаружить уже существующие проблемы.
Каждая найденная уязвимость сопровождается пошаговым объяснением и аннотациями к коду для последующего ручного анализа. Чтобы минимизировать ложные срабатывания, Aardvark пытается воспроизвести уязвимость в изолированной среде. Это позволяет подтвердить её эксплуатируемость и повысить достоверность выводов. Такой подход обеспечивает высокое качество отчётов и снижает нагрузку на команды безопасности.
После подтверждения уязвимости Aardvark автоматически генерирует патч с помощью интеграции с OpenAI Codex. Этот патч проходит дополнительную проверку самим агентом и прикрепляется к отчёту для быстрого применения разработчиками. Весь процесс встроен в существующие рабочие процессы — в частности, через GitHub — и не замедляет цикл разработки, предоставляя при этом конкретные и выполнимые рекомендации.
Помимо классических уязвимостей, Aardvark способен выявлять логические ошибки, неполные исправления и проблемы с конфиденциальностью. За несколько месяцев внутреннего использования в OpenAI и у внешних партнёров система уже обнаружила значимые уязвимости, включая сложные случаи, возникающие только при определённых условиях. В тестах на эталонных репозиториях Aardvark показал 92% полноты обнаружения как реальных, так и искусственно внесённых уязвимостей.
Особое внимание уделяется открытому программному обеспечению. Aardvark уже помог выявить и ответственно раскрыть десятки уязвимостей в open-source проектах, десять из которых получили идентификаторы CVE. OpenAI планирует предоставлять бесплатное сканирование для некоммерческих open-source репозиториев, внося вклад в безопасность всей экосистемы. При этом компания обновила политику раскрытия уязвимостей, сделав её более гибкой и ориентированной на сотрудничество с разработчиками.
Программное обеспечение стало основой современной инфраструктуры, а каждая уязвимость — потенциальной угрозой для бизнеса и общества. Согласно данным OpenAI, около 1,2% коммитов вносят ошибки, которые могут иметь серьёзные последствия. Aardvark предлагает новый, ориентированный на защиту подход: непрерывный, масштабируемый и интегрированный в процесс разработки. Сейчас система доступна в частной бете, и OpenAI приглашает организации и open-source проекты к участию для дальнейшего улучшения её возможностей.
#ai
Вчера OpenAI анонсировала Aardvark — автономного агентного исследователя безопасности, основанного на GPT‑5. Эта система призвана изменить подход к обнаружению и устранению уязвимостей в программном обеспечении, смещая баланс в пользу защитников. В условиях, когда ежегодно выявляется десятки тысяч новых уязвимостей, своевременное обнаружение и исправление критических ошибок становится вопросом не только качества кода, но и общей кибербезопасности инфраструктуры.
Aardvark представляет собой прорыв на стыке искусственного интеллекта и информационной безопасности. В отличие от традиционных методов анализа, таких как фаззинг или анализ состава программного обеспечения, Aardvark использует рассуждения на основе крупной языковой модели и взаимодействие с инструментами. Агент имитирует работу человека-исследователя: читает код, анализирует его поведение, пишет и запускает тесты, а также использует внешние утилиты для подтверждения своих выводов.
Процесс работы Aardvark построен на многоэтапном конвейере. На первом этапе система строит модель угроз для репозитория, отражая понимание его архитектуры и целей безопасности. Затем она отслеживает коммиты в реальном времени, сопоставляя изменения с общей моделью и выявляя потенциальные уязвимости. При первом подключении репозитория Aardvark также анализирует всю историю коммитов, чтобы обнаружить уже существующие проблемы.
Каждая найденная уязвимость сопровождается пошаговым объяснением и аннотациями к коду для последующего ручного анализа. Чтобы минимизировать ложные срабатывания, Aardvark пытается воспроизвести уязвимость в изолированной среде. Это позволяет подтвердить её эксплуатируемость и повысить достоверность выводов. Такой подход обеспечивает высокое качество отчётов и снижает нагрузку на команды безопасности.
После подтверждения уязвимости Aardvark автоматически генерирует патч с помощью интеграции с OpenAI Codex. Этот патч проходит дополнительную проверку самим агентом и прикрепляется к отчёту для быстрого применения разработчиками. Весь процесс встроен в существующие рабочие процессы — в частности, через GitHub — и не замедляет цикл разработки, предоставляя при этом конкретные и выполнимые рекомендации.
Помимо классических уязвимостей, Aardvark способен выявлять логические ошибки, неполные исправления и проблемы с конфиденциальностью. За несколько месяцев внутреннего использования в OpenAI и у внешних партнёров система уже обнаружила значимые уязвимости, включая сложные случаи, возникающие только при определённых условиях. В тестах на эталонных репозиториях Aardvark показал 92% полноты обнаружения как реальных, так и искусственно внесённых уязвимостей.
Особое внимание уделяется открытому программному обеспечению. Aardvark уже помог выявить и ответственно раскрыть десятки уязвимостей в open-source проектах, десять из которых получили идентификаторы CVE. OpenAI планирует предоставлять бесплатное сканирование для некоммерческих open-source репозиториев, внося вклад в безопасность всей экосистемы. При этом компания обновила политику раскрытия уязвимостей, сделав её более гибкой и ориентированной на сотрудничество с разработчиками.
Программное обеспечение стало основой современной инфраструктуры, а каждая уязвимость — потенциальной угрозой для бизнеса и общества. Согласно данным OpenAI, около 1,2% коммитов вносят ошибки, которые могут иметь серьёзные последствия. Aardvark предлагает новый, ориентированный на защиту подход: непрерывный, масштабируемый и интегрированный в процесс разработки. Сейчас система доступна в частной бете, и OpenAI приглашает организации и open-source проекты к участию для дальнейшего улучшения её возможностей.
#ai
🔥6🤔2
Rug Pull
Буквально «rug pull» означает действие, при котором кто-то внезапно выдергивает коврик из-под ног другого человека, в результате чего тот неожиданно падает.
В сфере децентрализованных финансов (DeFi) rug pull означает мошенничество, при котором разработчики продвигают новый токен или платформу, а затем внезапно выводят ликвидность (LP) или продают свои активы в больших количествах, что приводит к резкому падению стоимости токена.
Это одна из форм «риска централизации». Риск централизации охватывает широкий спектр уязвимостей, таких как контроль одной организацией над обновлениями, функциями приостановки или критическими параметрами. В этой статье мы сосредоточимся конкретно на rug pull с целью вывода средств, когда администраторы используют свои привилегии для вывода средств, внесенных пользователями или хранящихся в протоколе.
Административные роли необходимы для поддержания и обновления протоколов, но предоставление неограниченного доступа к активам создает значительную уязвимость. Если злоумышленник получит контроль над ключом администратора, он сможет вывести средства, фактически осуществив «rug pull», истощив резервы протокола. Даже без злого умысла, скомпрометированный ключ из-за нарушения безопасности может привести к серьезным последствиям, подорвав репутацию протокола и доверие пользователей.
Для того, чтобы снизить этот риск, смарт контракты должны быть разработаны таким образом, чтобы минимизировать влияние скомпрометированной или злонамеренной учетной записи администратора. Это включает в себя ограничение административных привилегий, строгий контроль всех действий администратора и внедрение мер безопасности для предотвращения односторонних или немедленных переводов активов. Административные функции должны рассматриваться как точная хирургическая процедура, необходимая, но тщательно ограниченная безопасным, заранее определенным объемом.
Недавним примером этой уязвимости является инцидент с протоколом Zunami в мае 2025 года, в результате которого было потеряно 500 000 долларов в виде залога zunUSD и zunETH. Согласно rekt.news, это не было сложным взломом с использованием флэш-кредитов или манипуляций с ценами. Скорее, это было простое злоупотребление чрезмерно мощной функцией администратора. Лицо с «доступом в режиме бога» вызвало функцию withdrawStuckToken(), опустошив содержимое хранилища.
Этот инцидент подчеркивает, что некоторые из наиболее разрушительных «взломов» являются результатом злонамеренного или неправомерного использования законных функций контракта авторизованными администраторами. Случай Zunami напоминает нам, что незащищенные аварийные функции могут стать инструментами для злоумышленников, если они не защищены должным образом.
Вот пример того, как злонамеренный или скомпрометированный администратор может опустошить хранилище и осуществить rug pull:
Буквально «rug pull» означает действие, при котором кто-то внезапно выдергивает коврик из-под ног другого человека, в результате чего тот неожиданно падает.
В сфере децентрализованных финансов (DeFi) rug pull означает мошенничество, при котором разработчики продвигают новый токен или платформу, а затем внезапно выводят ликвидность (LP) или продают свои активы в больших количествах, что приводит к резкому падению стоимости токена.
Это одна из форм «риска централизации». Риск централизации охватывает широкий спектр уязвимостей, таких как контроль одной организацией над обновлениями, функциями приостановки или критическими параметрами. В этой статье мы сосредоточимся конкретно на rug pull с целью вывода средств, когда администраторы используют свои привилегии для вывода средств, внесенных пользователями или хранящихся в протоколе.
Административные роли необходимы для поддержания и обновления протоколов, но предоставление неограниченного доступа к активам создает значительную уязвимость. Если злоумышленник получит контроль над ключом администратора, он сможет вывести средства, фактически осуществив «rug pull», истощив резервы протокола. Даже без злого умысла, скомпрометированный ключ из-за нарушения безопасности может привести к серьезным последствиям, подорвав репутацию протокола и доверие пользователей.
Для того, чтобы снизить этот риск, смарт контракты должны быть разработаны таким образом, чтобы минимизировать влияние скомпрометированной или злонамеренной учетной записи администратора. Это включает в себя ограничение административных привилегий, строгий контроль всех действий администратора и внедрение мер безопасности для предотвращения односторонних или немедленных переводов активов. Административные функции должны рассматриваться как точная хирургическая процедура, необходимая, но тщательно ограниченная безопасным, заранее определенным объемом.
Недавним примером этой уязвимости является инцидент с протоколом Zunami в мае 2025 года, в результате которого было потеряно 500 000 долларов в виде залога zunUSD и zunETH. Согласно rekt.news, это не было сложным взломом с использованием флэш-кредитов или манипуляций с ценами. Скорее, это было простое злоупотребление чрезмерно мощной функцией администратора. Лицо с «доступом в режиме бога» вызвало функцию withdrawStuckToken(), опустошив содержимое хранилища.
Этот инцидент подчеркивает, что некоторые из наиболее разрушительных «взломов» являются результатом злонамеренного или неправомерного использования законных функций контракта авторизованными администраторами. Случай Zunami напоминает нам, что незащищенные аварийные функции могут стать инструментами для злоумышленников, если они не защищены должным образом.
Вот пример того, как злонамеренный или скомпрометированный администратор может опустошить хранилище и осуществить rug pull:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract VulnerableVault {
address public admin;
// Maps user addresses to their deposited balances
mapping(address => uint256) public userBalances;
constructor() {
// The deployer of the contract becomes the admin
admin = msg.sender;
}
/// @notice Allows users to deposit Ether into the vault.
/// A 1% fee is applied to the deposit and sent to a fee pool.
function deposit() public payable {
require(msg.value > 0, "Deposit must be greater than zero");
// Calculate 1% fee. This fee theoretically belongs to the protocol.
uint256 fee = msg.value / 100; // 1% of deposited amount
uint256 amountToDeposit = msg.value - fee;
// User's balance is updated with their net deposit.
userBalances[msg.sender] += amountToDeposit;
// The 'fee' part of msg.value remains in the contract's total balance.
// It is not explicitly tracked in a separate variable, which is a design flaw
// if only fees should be withdrawable by admin.
}
/// @notice Allows users to withdraw their deposited funds.
/// @param amount The amount to withdraw.
function withdraw(uint256 amount) public {
require(userBalances[msg.sender] >= amount, "Insufficient balance");
// Decrement user's balance and transfer funds.
userBalances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
/// @notice Admin can withdraw *any* amount from the contract's balance.
/// This is the rug pull vulnerability! This simulates a `withdrawStuckToken()`-like function.
/// @param amount The amount to withdraw from the contract.
function adminRugPullWithdraw(uint256 amount) public {
require(msg.sender == admin, "Admin control required"); // Only admin can call.
// NO checks to ensure 'amount' is only from accrued fees.
// The admin can withdraw any amount up to the contract's total Ether balance (address(this).balance),
// effectively stealing user deposits, as user deposits also contribute to address(this).balance.
// This function treats all funds in the contract as if they are available for the admin to take.
require(address(this).balance >= amount, "Insufficient contract balance for withdrawal");
payable(admin).transfer(amount); // Transfer chosen amount to admin.
}
}
В этом контракте VulnerableVault функция adminRugPullWithdraw позволяет администратору снимать любую сумму эфира с общего баланса контракта. Это критическая уязвимость. Если ключ администратора скомпрометирован или если лицо, контролирующее ключ, решает действовать злонамеренно (как это было поставлено под сомнение в деле Zunami в отношении «внутренней работы»), оно может использовать эту функцию для снятия всего эфира, депонированного в хранилище, включая средства пользователей.
Оператор require только проверяет, что вызывающий является администратором, полностью игнорируя проверку суммы на соответствие тому, что должно быть законно снято (например, только комиссии протокола, а не основная сумма пользователя).
И для того, чтобы снизить эту уязвимость и предотвратить мошенничество, рассмотрите следующие надежные стратегии:
- Ограничьте доступ администратора к определенным средствам: ограничьте любую функцию вывода администратора только средствами, принадлежащими протоколу (например, собранными комиссиями, средствами казны), и никогда не допускайте вывода депозитов пользователей. Депозиты пользователей должны быть доступны для вывода только самим пользователям.
- Внедрение временных блокировок: для любых конфиденциальных административных действий, особенно тех, которые связаны с перемещением средств или изменением критических параметров, требуется временная блокировка. Эта задержка дает пользователям и системам мониторинга важную возможность обнаружить потенциально вредоносные транзакции и отреагировать (например, вывести свои средства или поднять тревогу) до того, как действие будет завершено. Это могло бы стать важным смягчающим фактором в случае Zunami, обеспечив защиту от немедленного опустошения счетов.
Вот пересмотренная версия контракта, в которой уязвимость устранена за счет введения внутреннего отслеживания комиссий и ограничения вывода средств администратором только на сумму начисленных комиссий:
В этой улучшенной версии SecuredVault функция adminWithdrawFees больше не взаимодействует напрямую с балансами пользователей. Средства пользователей защищены, поскольку они хранятся отдельно от переменной totalAccruedFees. Это снижает риск злоупотребления правами администратора в отношении пользователя, устраняя основную проблему, наблюдавшуюся при атаке Zunami.
#rugpull
Вот пересмотренная версия контракта, в которой уязвимость устранена за счет введения внутреннего отслеживания комиссий и ограничения вывода средств администратором только на сумму начисленных комиссий:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SecuredVault {
address public admin;
// Maps user addresses to their deposited balances
mapping(address => uint256) public userBalances;
// This variable *only* accounts for fees that the admin can withdraw.
// It's explicitly separated from user balances.
uint256 public totalAccruedFees;
constructor() {
// The deployer of the contract becomes the admin
admin = msg.sender;
}
/// @notice Allows users to deposit Ether into the vault.
/// A 1% fee is applied to the deposit and sent to a fee pool.
function deposit() public payable {
require(msg.value > 0, "Deposit must be greater than zero");
// Calculate 1% fee
uint256 fee = msg.value / 100; // 1% of deposited amount
uint256 amountToDeposit = msg.value - fee;
userBalances[msg.sender] += amountToDeposit; // User's principal
totalAccruedFees += fee; // Accrue the fee for the protocol
}
/// @notice Allows users to withdraw their deposited funds.
/// @param amount The amount to withdraw.
function withdraw(uint256 amount) public {
require(userBalances[msg.sender] >= amount, "Insufficient balance");
userBalances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
/// @notice Admin can only withdraw the total accrued fees.
/// This prevents the rug pull as admin cannot touch user principal.
/// @param amount The amount of fees the admin wishes to withdraw.
function adminWithdrawFees(uint256 amount) public {
require(msg.sender == admin, "Admin control required");
// Crucial check: Ensure admin only withdraws what's available in fees,
// as tracked separately from user principal.
require(totalAccruedFees >= amount, "Insufficient accrued fees for withdrawal");
totalAccruedFees -= amount; // Deduct the withdrawn amount from available fees
payable(admin).transfer(amount); // Transfer only the requested fee amount to admin
}
}
В этой улучшенной версии SecuredVault функция adminWithdrawFees больше не взаимодействует напрямую с балансами пользователей. Средства пользователей защищены, поскольку они хранятся отдельно от переменной totalAccruedFees. Это снижает риск злоупотребления правами администратора в отношении пользователя, устраняя основную проблему, наблюдавшуюся при атаке Zunami.
#rugpull
👍7
Solodit запускает свое API
Самый популярный сайт для поиска уязвимостей по отчетам запускает свое API для тех, кто создает ботов и ИИ агентов по анализу смарт контрактов. Довольно горячая тема на сегодняшний день, поэтому команда прекрасно попадает в тренды запросов. По словам разработчика из команды, за первый день они собрали уже более 400 заявок на закрытое бета тестирование!
Я сам долгое время собирал, сортировал и анализировал подобные отчеты, и теперь очень интересно как они решат несколько следующих проблем.
Во-первых, качество отчетов. На текущий момент на Solodit я нашел всего 43 486 результатов:
1. High - 6859 results
2. Medium - 12263 results
3. Low - 21032 results
4. Gas - 3332 results
Заметьте, что Low составляет большую часть. С уверенностью могу сказать, что половина Medium - будет либо повторами, либо мусором (вспомните хотя бы Medium уязвимости, которые были таковыми в 2023 году и сейчас уже в статусе QA), а также около четверти от High - (повторы и "натянутые" Medium, от аудиторов, которые хотели доказать свою значимость). В итого остается около 10 000 адекватных отчетов. Можно также смело убрать из них половину тех, где будет не качественное описание уязвимостей, без примеров кода и POC.
Я уже делал небольшой обзор на эту проблему в этом посте: https://news.1rj.ru/str/solidityset/1482
Во-вторых, проблема поиска. Как они решили проблему поиска и ранжирования уязвимостей? Да, понятно можно поискать по ключевым словам: reentrancy, replay, defi. В результате будет определенное количество уязвимостей. И да, мы можем проранжировать их через опцию High/Medium/Low, отсеяв, например, последние два. Но тогда вероятные релевантные баги из Medium будут отброшены! А это все может стать хорошей подсказкой к поиску других багов в протоколе, который вы аудируете сейчас.
В-третьих, доступы по API. С большой долей уверенности скажу, что будут немного бесплатных запросов в месяц для всех пользователей, и увеличенные лимиты - для платных подписчиков. Делать все открытым и бесплатным - значит обречь себя не нескончаемый поток запросов, каких может быть тысячи в секунду! А это большая нагрузка и на сервер и на ожидание других пользователей. Остается вопрос цены. Не думаю, что будет большой, но все же.
В общем, идея очень здравая и будет интересно наблюдать как Cyfrin сможет развить ее, учитывая количество всех остальных проектов, которые они ведут.
А вы будете пользоваться новым сервисом API для поиска багов? Как бы вы решили эти проблемы?
#api #bugs
Самый популярный сайт для поиска уязвимостей по отчетам запускает свое API для тех, кто создает ботов и ИИ агентов по анализу смарт контрактов. Довольно горячая тема на сегодняшний день, поэтому команда прекрасно попадает в тренды запросов. По словам разработчика из команды, за первый день они собрали уже более 400 заявок на закрытое бета тестирование!
Я сам долгое время собирал, сортировал и анализировал подобные отчеты, и теперь очень интересно как они решат несколько следующих проблем.
Во-первых, качество отчетов. На текущий момент на Solodit я нашел всего 43 486 результатов:
1. High - 6859 results
2. Medium - 12263 results
3. Low - 21032 results
4. Gas - 3332 results
Заметьте, что Low составляет большую часть. С уверенностью могу сказать, что половина Medium - будет либо повторами, либо мусором (вспомните хотя бы Medium уязвимости, которые были таковыми в 2023 году и сейчас уже в статусе QA), а также около четверти от High - (повторы и "натянутые" Medium, от аудиторов, которые хотели доказать свою значимость). В итого остается около 10 000 адекватных отчетов. Можно также смело убрать из них половину тех, где будет не качественное описание уязвимостей, без примеров кода и POC.
Я уже делал небольшой обзор на эту проблему в этом посте: https://news.1rj.ru/str/solidityset/1482
Во-вторых, проблема поиска. Как они решили проблему поиска и ранжирования уязвимостей? Да, понятно можно поискать по ключевым словам: reentrancy, replay, defi. В результате будет определенное количество уязвимостей. И да, мы можем проранжировать их через опцию High/Medium/Low, отсеяв, например, последние два. Но тогда вероятные релевантные баги из Medium будут отброшены! А это все может стать хорошей подсказкой к поиску других багов в протоколе, который вы аудируете сейчас.
В-третьих, доступы по API. С большой долей уверенности скажу, что будут немного бесплатных запросов в месяц для всех пользователей, и увеличенные лимиты - для платных подписчиков. Делать все открытым и бесплатным - значит обречь себя не нескончаемый поток запросов, каких может быть тысячи в секунду! А это большая нагрузка и на сервер и на ожидание других пользователей. Остается вопрос цены. Не думаю, что будет большой, но все же.
В общем, идея очень здравая и будет интересно наблюдать как Cyfrin сможет развить ее, учитывая количество всех остальных проектов, которые они ведут.
А вы будете пользоваться новым сервисом API для поиска багов? Как бы вы решили эти проблемы?
#api #bugs
👍5🔥1
Небольшой отпуск
За последние несколько месяцев на меня обрушился настоящий поток новой информации, активной работы над проектами и погружения в ранее незнакомую область. И чтобы не выгореть и немного восстановить баланс, я решил устроить себе короткий перерыв на следующей неделе — в том числе наверстать пару игр, которые давно ждут в библиотеке Steam с летней распродажи. До конца года впереди ещё немало задач, так что эта пауза придётся очень кстати.
Пользуясь моментом, хочу попросить вас поделиться актуальными темами для постов, связанных с Solidity и Web3, которые появились или стали особенно заметны за последние месяцы. Из‑за временного фокуса на обучении я немного отстал от новостей и трендов в этой сфере, и буду благодарен за любые наводки.
В остальном — всё в порядке. Если понадобится, я всегда найду время ответить в чате.
#timeout
За последние несколько месяцев на меня обрушился настоящий поток новой информации, активной работы над проектами и погружения в ранее незнакомую область. И чтобы не выгореть и немного восстановить баланс, я решил устроить себе короткий перерыв на следующей неделе — в том числе наверстать пару игр, которые давно ждут в библиотеке Steam с летней распродажи. До конца года впереди ещё немало задач, так что эта пауза придётся очень кстати.
Пользуясь моментом, хочу попросить вас поделиться актуальными темами для постов, связанных с Solidity и Web3, которые появились или стали особенно заметны за последние месяцы. Из‑за временного фокуса на обучении я немного отстал от новостей и трендов в этой сфере, и буду благодарен за любые наводки.
В остальном — всё в порядке. Если понадобится, я всегда найду время ответить в чате.
#timeout
🐳5❤1🤯1
Погружение в Core Solidity. Часть 1
Потихоньку выхожу из отпуска, вспоминаю, на чем остановился неделю назад и планирую следующую часть работы до конца года.
Если кратко: я закончил писать проект своего ассистента для аудита смарт контрактов, и теперь нужно добавить общий mcp сервер и систему автоматизации добавлений в базу данных, что поможет всегда держать ее актуальной. Кстати, это будут первые проекты на вайб кодинге. После тестов и настройки, я покажу их вам.
А пока, давайте поговорим об обновлении языка Solidity - Core Solidity, о котором я писал не так давно. Разработчики выкатили небольшое описание функций системы, которые мы рассмотрим на этой неделе.
P.S. Далее будет идти перевод статьи от имени разработчиков.
Solidity — это самый широко используемый язык для написания смарт-контрактов. Он надёжен, заслуживает доверия и сегодня обеспечивает сохранность активов на сумму в сотни миллиардов долларов. Мы гордимся этим успехом и безупречной репутацией генерации безопасного кода. Однако пользователи Solidity прекрасно осознают некоторые его ограничения. Система типов зачастую недостаточно выразительна: она не позволяет создавать многократно используемый код библиотек или гарантировать ключевые свойства безопасности. Язык почти не поддерживает вычисления на этапе компиляции. Многие функции реализованы несогласованно или не всегда работают так, как ожидается.
Оказалось чрезвычайно сложно устранить эти ограничения в рамках текущей реализации. Обновления приходится вносить скорее стихийно, и каждое новое дополнение усложняет анализ корректности последующих изменений. Мы не были уверены, что сможем безопасно расширять язык именно таким образом, чтобы внедрить те функции, которых требуют наши пользователи и которые, по нашему мнению, необходимы для того, чтобы соответствовать постоянно растущему масштабу систем, разрабатываемых на Solidity.
Core Solidity — наше решение этой проблемы. Это полная переработка системы типов и фронтенд/мидл-энд компилятора Solidity, которая позволит:
- внедрить мощные новые возможности,
- заложить прочную основу для корректности компилятора при дальнейшем расширении языка,
- дать возможность авторам библиотек активно участвовать и поддержать сообществом управляемый процесс развития языка,
- расширить возможности инструментов верификации и анализа.
Помимо расширения и развития языка, мы также собираемся убрать или переработать некоторые существующие функции. Уже точно решено, что мы полностью уберём наследование. Другие изменения пока менее определены, но мы рассматриваем возможность замены или переработки таких механизмов, как try/catch, библиотеки, указатели на функции, преобразование типов и указание мест хранения данных.
Тем не менее, Core Solidity в сравнении с классическим Solidity — это не новый язык, а по большей части его расширение. Он сохранит знакомый внешний вид и архитектуру, и большинство концепций классического Solidity в нём останутся без изменений.
На данный момент у нас уже есть рабочий прототип Core Solidity. Большинство примеров из этого поста успешно проходят проверку типов и могут генерировать исполняемый код. Некоторые примеры используют ещё не реализованный синтаксис и будут компилироваться в будущем. Основы теории типов уже стабильны, но перед тем, как мы сочтём систему типов завершённой, мы хотим добавить как минимум поддержку вычислений во время компиляции и модули. Впереди ещё много работы по созданию стандартной библиотеки и достижению функциональной эквивалентности с классическим Solidity.
Хотя прототип уже работает, он пока не оптимизирован для удобства пользователей. Мы активно продолжаем работу над прототипом, и новые функции будут постепенно появляться в репозитории проекта для обратной связи и экспериментов. Мы с нетерпением ждём ваших комментариев!
Потихоньку выхожу из отпуска, вспоминаю, на чем остановился неделю назад и планирую следующую часть работы до конца года.
Если кратко: я закончил писать проект своего ассистента для аудита смарт контрактов, и теперь нужно добавить общий mcp сервер и систему автоматизации добавлений в базу данных, что поможет всегда держать ее актуальной. Кстати, это будут первые проекты на вайб кодинге. После тестов и настройки, я покажу их вам.
А пока, давайте поговорим об обновлении языка Solidity - Core Solidity, о котором я писал не так давно. Разработчики выкатили небольшое описание функций системы, которые мы рассмотрим на этой неделе.
P.S. Далее будет идти перевод статьи от имени разработчиков.
Solidity — это самый широко используемый язык для написания смарт-контрактов. Он надёжен, заслуживает доверия и сегодня обеспечивает сохранность активов на сумму в сотни миллиардов долларов. Мы гордимся этим успехом и безупречной репутацией генерации безопасного кода. Однако пользователи Solidity прекрасно осознают некоторые его ограничения. Система типов зачастую недостаточно выразительна: она не позволяет создавать многократно используемый код библиотек или гарантировать ключевые свойства безопасности. Язык почти не поддерживает вычисления на этапе компиляции. Многие функции реализованы несогласованно или не всегда работают так, как ожидается.
Оказалось чрезвычайно сложно устранить эти ограничения в рамках текущей реализации. Обновления приходится вносить скорее стихийно, и каждое новое дополнение усложняет анализ корректности последующих изменений. Мы не были уверены, что сможем безопасно расширять язык именно таким образом, чтобы внедрить те функции, которых требуют наши пользователи и которые, по нашему мнению, необходимы для того, чтобы соответствовать постоянно растущему масштабу систем, разрабатываемых на Solidity.
Core Solidity — наше решение этой проблемы. Это полная переработка системы типов и фронтенд/мидл-энд компилятора Solidity, которая позволит:
- внедрить мощные новые возможности,
- заложить прочную основу для корректности компилятора при дальнейшем расширении языка,
- дать возможность авторам библиотек активно участвовать и поддержать сообществом управляемый процесс развития языка,
- расширить возможности инструментов верификации и анализа.
Помимо расширения и развития языка, мы также собираемся убрать или переработать некоторые существующие функции. Уже точно решено, что мы полностью уберём наследование. Другие изменения пока менее определены, но мы рассматриваем возможность замены или переработки таких механизмов, как try/catch, библиотеки, указатели на функции, преобразование типов и указание мест хранения данных.
Тем не менее, Core Solidity в сравнении с классическим Solidity — это не новый язык, а по большей части его расширение. Он сохранит знакомый внешний вид и архитектуру, и большинство концепций классического Solidity в нём останутся без изменений.
На данный момент у нас уже есть рабочий прототип Core Solidity. Большинство примеров из этого поста успешно проходят проверку типов и могут генерировать исполняемый код. Некоторые примеры используют ещё не реализованный синтаксис и будут компилироваться в будущем. Основы теории типов уже стабильны, но перед тем, как мы сочтём систему типов завершённой, мы хотим добавить как минимум поддержку вычислений во время компиляции и модули. Впереди ещё много работы по созданию стандартной библиотеки и достижению функциональной эквивалентности с классическим Solidity.
Хотя прототип уже работает, он пока не оптимизирован для удобства пользователей. Мы активно продолжаем работу над прототипом, и новые функции будут постепенно появляться в репозитории проекта для обратной связи и экспериментов. Мы с нетерпением ждём ваших комментариев!
Примечание о синтаксисе
Большая часть проделанной на сегодняшний день работы была сосредоточена на проектировании и реализации системы типов и связанного с ней конвейера генерации кода вплоть до Yul. Чтобы не увязнуть в бесконечных спорах о деталях синтаксиса и как можно скорее проверить наши ключевые идеи на рабочей реализации, мы решили использовать временный (провизорный) синтаксис. До релиза можно ожидать значительных изменений. В настоящее время мы стремимся в итоге максимально приблизить синтаксис Core Solidity к синтаксису классического Solidity. Что касается нового синтаксиса, то окончательная его версия, скорее всего, будет ближе к таким языкам, как TypeScript или Rust.
Новые языковые возможности
Core Solidity заимствует идеи из чистых функциональных языков программирования (например, Haskell, Lean), а также из современных системных языков (например, Rust, Zig). Мы расширяем Solidity следующими новыми возможностями:
- Алгебраические типы данных (известные также как суммы и произведения типов) и сопоставление с образцом (pattern matching)
- Обобщения (generics) / параметрический полиморфизм
- Трейты (traits) / классы типов (type classes)
- Вывод типов (type inference)
- Функции высшего порядка и анонимные функции
- Вычисления на этапе компиляции
Мы считаем, что эти фундаментальные конструкции позволят разработчикам создавать более мощные абстракции, писать более модульный и многократно используемый код, а также задействовать систему типов для обеспечения свойств безопасности.
Мы продолжим поддерживать низкоуровневый доступ к EVM, который часто необходим в промышленных реализациях: встроенная ассемблерная вставка (assembly) останется базовой языковой конструкцией, и мы расширим блоки ассемблера возможностью напрямую вызывать функции, определённые на высокоуровневом языке. Пользователи смогут отключать встроенные абстракции (например, автоматическую генерацию диспетчеризации контрактов, декодирование ABI, генерацию стандартной схемы хранения данных), следуя философии «плати только за то, чем пользуешься», характерной для таких языков, как Rust и C++.
Далее поговорим подробнее и с примерами.
#core
Большая часть проделанной на сегодняшний день работы была сосредоточена на проектировании и реализации системы типов и связанного с ней конвейера генерации кода вплоть до Yul. Чтобы не увязнуть в бесконечных спорах о деталях синтаксиса и как можно скорее проверить наши ключевые идеи на рабочей реализации, мы решили использовать временный (провизорный) синтаксис. До релиза можно ожидать значительных изменений. В настоящее время мы стремимся в итоге максимально приблизить синтаксис Core Solidity к синтаксису классического Solidity. Что касается нового синтаксиса, то окончательная его версия, скорее всего, будет ближе к таким языкам, как TypeScript или Rust.
Новые языковые возможности
Core Solidity заимствует идеи из чистых функциональных языков программирования (например, Haskell, Lean), а также из современных системных языков (например, Rust, Zig). Мы расширяем Solidity следующими новыми возможностями:
- Алгебраические типы данных (известные также как суммы и произведения типов) и сопоставление с образцом (pattern matching)
- Обобщения (generics) / параметрический полиморфизм
- Трейты (traits) / классы типов (type classes)
- Вывод типов (type inference)
- Функции высшего порядка и анонимные функции
- Вычисления на этапе компиляции
Мы считаем, что эти фундаментальные конструкции позволят разработчикам создавать более мощные абстракции, писать более модульный и многократно используемый код, а также задействовать систему типов для обеспечения свойств безопасности.
Мы продолжим поддерживать низкоуровневый доступ к EVM, который часто необходим в промышленных реализациях: встроенная ассемблерная вставка (assembly) останется базовой языковой конструкцией, и мы расширим блоки ассемблера возможностью напрямую вызывать функции, определённые на высокоуровневом языке. Пользователи смогут отключать встроенные абстракции (например, автоматическую генерацию диспетчеризации контрактов, декодирование ABI, генерацию стандартной схемы хранения данных), следуя философии «плати только за то, чем пользуешься», характерной для таких языков, как Rust и C++.
Далее поговорим подробнее и с примерами.
#core
🔥6🤯3🤔2
Погружение в Core Solidity. Часть 2
Алгебраические типы данных и сопоставление с образцом
Алгебраические типы данных (ADT, Algebraic Data Types) предоставляют принципиальную основу для моделирования данных за счёт комбинирования суммарных (sum) и произведённых (product) типов. Суммарные типы являются расширением перечислений (enums) из классического Solidity. Они представляют исключающие друг друга варианты: значение принадлежит ровно одному из возможных вариантов. Произведённые типы объединяют несколько значений в структурированные кортежи. На основе этих двух примитивов можно конструировать точные типы, делающие недопустимые состояния полностью невозможными для представления, что позволяет системе типов обеспечивать соблюдение инвариантов полностью на этапе компиляции.
Начнём с очень простого типа:
Левая часть приведённого выше выражения определяет имя нового типа (Bool), а правая часть задаёт множество значений, составляющих тип Bool (True или False).
С помощью ADT также можно реализовать те же самые паттерны, что и пользовательские типы-значения (User Defined Value Types) в классическом Solidity. Например, значение с фиксированной точкой и 18 знаками после запятой («wad») можно представить следующим образом:
Тип wad (слева) имеет единственный конструктор значений wad (справа), который хранит значение типа uint256 в качестве своего внутреннего представления. Имена типов и конструкторы значений находятся в отдельных пространствах имён, поэтому могут совпадать. Простые обёрточные типы подобного рода будут полностью удалены компилятором при трансляции в Yul, то есть тип wad будет иметь точно такое же представление во время выполнения, как и uint256.
Теперь можно определить процедуру умножения чисел с фиксированной точкой с проверкой типов. Для этого потребуется извлечь внутреннее значение uint256, произвести над ним необходимые операции и обернуть результат в новый конструктор wad. Для распаковки воспользуемся сопоставлением с образцом (pattern matching). Сопоставление с образцом — это механизм управления потоком выполнения, позволяющий деконструировать и анализировать данные по их структуре. Вместо громоздких цепочек if-else можно писать декларативные выражения, полностью перебирающие все возможные значения проверяемого типа.
Тип AuctionState, ниже, имеет четыре альтернативных конструктора значений:
- NotStarted указывает, что аукцион ещё не начался, и хранит резервную цену;
- Active означает, что аукцион идёт, и хранит текущую максимальную ставку и адрес того, кто её сделал;
- Ended представляет успешно завершившийся аукцион с максимальной ставкой и адресом победителя;
- Cancelled описывает отменённый аукцион и хранит максимальную ставку и адрес предполагаемого победителя на момент отмены.
Теперь можно определить функцию processAuction, которая изменяет состояние аукциона в зависимости от текущего состояния и значения msg.value. Выражение match позволяет выполнить исчерпывающий разбор всех возможных состояний. Случай _ в конце конструкции match является обработчиком по умолчанию для всех оставшихся состояний, которые явно не были перечислены. Компилятор гарантирует полноту такого разбора, требуя, чтобы каждое возможное состояние обрабатывалось ровно один раз.
#core
Алгебраические типы данных и сопоставление с образцом
Алгебраические типы данных (ADT, Algebraic Data Types) предоставляют принципиальную основу для моделирования данных за счёт комбинирования суммарных (sum) и произведённых (product) типов. Суммарные типы являются расширением перечислений (enums) из классического Solidity. Они представляют исключающие друг друга варианты: значение принадлежит ровно одному из возможных вариантов. Произведённые типы объединяют несколько значений в структурированные кортежи. На основе этих двух примитивов можно конструировать точные типы, делающие недопустимые состояния полностью невозможными для представления, что позволяет системе типов обеспечивать соблюдение инвариантов полностью на этапе компиляции.
Начнём с очень простого типа:
data Bool = True | False
Левая часть приведённого выше выражения определяет имя нового типа (Bool), а правая часть задаёт множество значений, составляющих тип Bool (True или False).
С помощью ADT также можно реализовать те же самые паттерны, что и пользовательские типы-значения (User Defined Value Types) в классическом Solidity. Например, значение с фиксированной точкой и 18 знаками после запятой («wad») можно представить следующим образом:
data wad = wad(uint256)
Тип wad (слева) имеет единственный конструктор значений wad (справа), который хранит значение типа uint256 в качестве своего внутреннего представления. Имена типов и конструкторы значений находятся в отдельных пространствах имён, поэтому могут совпадать. Простые обёрточные типы подобного рода будут полностью удалены компилятором при трансляции в Yul, то есть тип wad будет иметь точно такое же представление во время выполнения, как и uint256.
Теперь можно определить процедуру умножения чисел с фиксированной точкой с проверкой типов. Для этого потребуется извлечь внутреннее значение uint256, произвести над ним необходимые операции и обернуть результат в новый конструктор wad. Для распаковки воспользуемся сопоставлением с образцом (pattern matching). Сопоставление с образцом — это механизм управления потоком выполнения, позволяющий деконструировать и анализировать данные по их структуре. Вместо громоздких цепочек if-else можно писать декларативные выражения, полностью перебирающие все возможные значения проверяемого типа.
let WAD = 10 ** 18;
function wmul(lhs : wad, rhs : wad) -> wad {
match (lhs, rhs) {
| (wad(l), wad(r)) => return wad((l * r) / WAD);
}
}
Тип AuctionState, ниже, имеет четыре альтернативных конструктора значений:
- NotStarted указывает, что аукцион ещё не начался, и хранит резервную цену;
- Active означает, что аукцион идёт, и хранит текущую максимальную ставку и адрес того, кто её сделал;
- Ended представляет успешно завершившийся аукцион с максимальной ставкой и адресом победителя;
- Cancelled описывает отменённый аукцион и хранит максимальную ставку и адрес предполагаемого победителя на момент отмены.
Теперь можно определить функцию processAuction, которая изменяет состояние аукциона в зависимости от текущего состояния и значения msg.value. Выражение match позволяет выполнить исчерпывающий разбор всех возможных состояний. Случай _ в конце конструкции match является обработчиком по умолчанию для всех оставшихся состояний, которые явно не были перечислены. Компилятор гарантирует полноту такого разбора, требуя, чтобы каждое возможное состояние обрабатывалось ровно один раз.
function processAuction(state: AuctionState) -> AuctionState {
match state {
| NotStarted(reserve) =>
require(msg.value >= reserve);
return Active(msg.value, msg.sender);
| Active(currentBid, bidder) =>
require(msg.value > currentBid);
transferFunds(bidder, currentBid);
return Active(msg.value, msg.sender);
| _ => return state;
}
}#core
👍8🔥2
Погружение в Core Solidity. Часть 3
Обобщения и классы типов
Core Solidity вводит два новых механизма для повторного использования кода и полиморфизма: обобщения (generics) и классы типов (иногда также называемые трейтами, traits).
Обобщения реализуют параметрический полиморфизм: они позволяют писать функции и структуры данных, работающие одинаково для всех типов. В качестве примера определим полиморфную функцию тождества:
Здесь forall вводит новую переменную-тип T, область видимости которой ограничена определением функции.
Можно также определять обобщённые типы. Например, следующий тип Result, параметризованный типом полезной нагрузки в случае ошибки:
Обобщения весьма мощны, но сами по себе довольно ограничены. Большинство интересных операций не определены для всех типов вообще. Классы типов решают эту проблему: они позволяют задавать перегруженные, специфичные для каждого типа реализации одной и той же сигнатуры функции. В сочетании с ограничениями классов типов они предоставляют возможность писать обобщённые функции, полиморфные лишь над ограниченным подмножеством типов.
Класс типов — это просто спецификация интерфейса. Рассмотрим, например, определение класса типов, которые поддерживают операцию умножения:
Вместо конкретной функции wmul, которую мы определили выше для нашего типа wad с фиксированной точкой, более идиоматично создать экземпляр (в терминологии Rust — impl) класса типов Mul для wad. Это даёт единообразный синтаксис умножения для всех типов и позволяет использовать wad в функциях, обобщённых над любыми типами, реализующими Mul:
Если мы хотим написать функцию, принимающую любой тип, для которого определён экземпляр Mul, необходимо добавить ограничение в сигнатуру:
Простые обёрточные типы вроде wad встречаются очень часто. Один из особенно полезных классов типов при работе с ними — Typedef:
Функции abs (абстрагирование) и rep (представление) позволяют единообразно преобразовывать обёрточные типы во внутренние и наоборот, избегая синтаксического шума, связанного с необходимостью использовать сопоставление с образцом каждый раз при распаковке значения. Экземпляр для wad выглядел бы так:
Обратите внимание: параметры, следующие после имени класса (например, U в определении Typedef выше), являются «слабыми» — их значение однозначно определяется значением параметра T. Если вы знакомы с Haskell или Rust, то это по сути ассоциированный тип (associated type) (хотя, для тех, кто разбирается в системах типов, реализовано это с помощью ограниченной формы функциональных зависимостей). Проще говоря, для wad можно определить только один экземпляр Typedef: компилятор не разрешит одновременно объявить и wad:Typedef(uint256), и wad:Typedef(uint128). Это ограничение делает вывод типов значительно более предсказуемым и надёжным, избегая многих неоднозначностей, присущих полноценным многопараметрическим классам типов.
Обобщения и классы типов
Core Solidity вводит два новых механизма для повторного использования кода и полиморфизма: обобщения (generics) и классы типов (иногда также называемые трейтами, traits).
Обобщения реализуют параметрический полиморфизм: они позволяют писать функции и структуры данных, работающие одинаково для всех типов. В качестве примера определим полиморфную функцию тождества:
forall T . function identity(x : T) -> T {
return x;
}Здесь forall вводит новую переменную-тип T, область видимости которой ограничена определением функции.
Можно также определять обобщённые типы. Например, следующий тип Result, параметризованный типом полезной нагрузки в случае ошибки:
data Result(T) = Ok | Err(T)
Обобщения весьма мощны, но сами по себе довольно ограничены. Большинство интересных операций не определены для всех типов вообще. Классы типов решают эту проблему: они позволяют задавать перегруженные, специфичные для каждого типа реализации одной и той же сигнатуры функции. В сочетании с ограничениями классов типов они предоставляют возможность писать обобщённые функции, полиморфные лишь над ограниченным подмножеством типов.
Класс типов — это просто спецификация интерфейса. Рассмотрим, например, определение класса типов, которые поддерживают операцию умножения:
forall T . class T:Mul {
function mul(lhs : T, rhs : T) -> T;
}Вместо конкретной функции wmul, которую мы определили выше для нашего типа wad с фиксированной точкой, более идиоматично создать экземпляр (в терминологии Rust — impl) класса типов Mul для wad. Это даёт единообразный синтаксис умножения для всех типов и позволяет использовать wad в функциях, обобщённых над любыми типами, реализующими Mul:
instance wad:Mul {
function mul(lhs : wad, rhs : wad) -> wad {
return wmul(lhs, rhs);
}
}Если мы хотим написать функцию, принимающую любой тип, для которого определён экземпляр Mul, необходимо добавить ограничение в сигнатуру:
forall T . T:Mul => function square(val : T) -> T {
return Mul.mul(val, val);
}Простые обёрточные типы вроде wad встречаются очень часто. Один из особенно полезных классов типов при работе с ними — Typedef:
forall T U . class T:Typedef(U) {
function abs(x : U) -> T;
function rep(x : T) -> U;
}Функции abs (абстрагирование) и rep (представление) позволяют единообразно преобразовывать обёрточные типы во внутренние и наоборот, избегая синтаксического шума, связанного с необходимостью использовать сопоставление с образцом каждый раз при распаковке значения. Экземпляр для wad выглядел бы так:
instance wad:Typedef(uint256) {
function abs(u : uint256) -> wad {
return wad(u);
}
function rep(x : wad) -> uint256 {
match x {
| wad(u) => return u;
}
}
}Обратите внимание: параметры, следующие после имени класса (например, U в определении Typedef выше), являются «слабыми» — их значение однозначно определяется значением параметра T. Если вы знакомы с Haskell или Rust, то это по сути ассоциированный тип (associated type) (хотя, для тех, кто разбирается в системах типов, реализовано это с помощью ограниченной формы функциональных зависимостей). Проще говоря, для wad можно определить только один экземпляр Typedef: компилятор не разрешит одновременно объявить и wad:Typedef(uint256), и wad:Typedef(uint128). Это ограничение делает вывод типов значительно более предсказуемым и надёжным, избегая многих неоднозначностей, присущих полноценным многопараметрическим классам типов.
❤2
В качестве реального примера того, как обобщения и ограничения через классы типов помогают устранить шаблонный и повторяющийся код, сравним комбинаторный взрыв перегрузок, необходимых для реализации console.log в библиотеке forge-std, с одной обобщённой функцией в Core Solidity, которая покрывает функциональность всех перегрузок с одним аргументом из оригинальной библиотеки. Слово word в этой реализации обозначает низкоуровневый тип, представляющий переменную на языке Yul, и является единственным типом, который можно передавать в блоки assembly и получать из них.
Подобно Rust и Lean, все вызовы классов типов и обобщённых функций полностью мономорфизируются (monomorphized) на этапе компиляции. Это означает, что полиморфные функции не несут накладных расходов во время выполнения по сравнению с полностью конкретизированными функциями. Хотя это действительно приводит к тому, что скомпилированный код для EVM может содержать несколько специализированных версий одной и той же обобщённой функции, это не увеличивает размер бинарного файла по сравнению с классическим Solidity, где для получения эквивалентной функциональности в любом случае потребовалось бы определять несколько отдельных функций. Мы считаем такой компромисс полностью оправданным для нашей предметной области.
#core
forall T . T:ABIEncode => function log(val : T) {
let CONSOLE_ADDRESS : word = 0x000000000000000000636F6e736F6c652e6c6f67;
let payload = abi_encode(val);
// извлекаем внутреннее представление payload как word
let ptr = Typedef.rep(payload);
assembly {
pop(
staticcall(
gas(),
CONSOLE_ADDRESS,
add(ptr, 32),
mload(ptr),
0,
0
)
)
}
}Подобно Rust и Lean, все вызовы классов типов и обобщённых функций полностью мономорфизируются (monomorphized) на этапе компиляции. Это означает, что полиморфные функции не несут накладных расходов во время выполнения по сравнению с полностью конкретизированными функциями. Хотя это действительно приводит к тому, что скомпилированный код для EVM может содержать несколько специализированных версий одной и той же обобщённой функции, это не увеличивает размер бинарного файла по сравнению с классическим Solidity, где для получения эквивалентной функциональности в любом случае потребовалось бы определять несколько отдельных функций. Мы считаем такой компромисс полностью оправданным для нашей предметной области.
#core
👍6❤1
Погружение в Core Solidity. Часть 4
Функции высшего порядка и анонимные функции
Функции обладают статусом «первоклассных» объектов в системе типов, что позволяет использовать их в качестве параметров, возвращаемых значений и присваиваемых сущностей.
В качестве примера рассмотрим следующий фрагмент, реализующий пользовательскую декодировку ABI для тройки логических значений из одного слова:
Функция unpack_bools реализует пользовательскую декодировку ABI. Она является функцией высшего порядка, которая «оборачивает» входную функцию, принимающую три отдельных логических значения и возвращающую значение произвольного типа T, извлекая аргументы из трёх младших битов входного слова. Такой пример невозможно реализовать в классическом Solidity, даже с использованием модификаторов, поскольку они не могут изменять аргументы, передаваемые внутрь оборачиваемой функции.
Кроме того, поддерживается определение (некурсивных) анонимных функций с помощью ключевого слова lam. Функции, определённые таким образом, могут захватывать значения из области видимости, в которой они объявлены. В качестве примера рассмотрим вспомогательную функцию для тестирования, подсчитывающую количество вызовов произвольной функции:
Реализация здесь аналогична тому, как это сделано в системных языках, таких как Rust и C++: компилятор генерирует уникальный тип для каждой анонимной функции, содержащей захваченные значения, а эти уникальные типы становятся вызываемыми за счёт принадлежности к специальному типовому классу «вызываемых» (invokable), подобно трейту Fn в Rust. Такой подход обеспечивает высокую эффективность с точки зрения затрат газа во время выполнения.
Вывод типов
Core Solidity поддерживает вывод типов почти в любом контексте. Аннотации типов обычно требуются только тогда, когда это желательно для улучшения читаемости или понимания кода. Алгоритм вывода типов разрешим, а ситуации, в которых возникает неоднозначность и требуется явная аннотация, крайне ограничены. Благодаря этому удаётся избавиться от большого количества синтаксического шума, присущего классическому Solidity.
Например, присваивание выражения переменной в классическом Solidity нередко приводит к избыточным аннотациям, даже если типы уже присутствуют в самом выражении:
То же самое определение в Core Solidity выглядит значительно чище:
Ещё одна частая причина раздражения при работе с классическим Solidity — синтаксический шум при определении литералов массивов. Рассмотрим следующий фрагмент:
Это объявление отвергается компилятором классического Solidity со следующей ошибкой:
Причина ошибки заключается в том, что классический Solidity реализует ограниченную и специализированную форму вывода типов для литералов массивов: тип элементов массива определяется как тип первого выражения в списке, к которому можно неявно привести все остальные элементы (в данном случае — uint8). Затем компилятор выдаёт ошибку несоответствия типов при попытке присвоить это значение переменной несовместимого типа.
Функции высшего порядка и анонимные функции
Функции обладают статусом «первоклассных» объектов в системе типов, что позволяет использовать их в качестве параметров, возвращаемых значений и присваиваемых сущностей.
В качестве примера рассмотрим следующий фрагмент, реализующий пользовательскую декодировку ABI для тройки логических значений из одного слова:
forall T . function unpack_bools(fn : (bool, bool, bool) -> T) -> ((word) -> T) {
return lam (bools : word) -> {
let wordToBool = lam (w : word) { return w > 0; };
// extract the right-most bit from `bools`
let b0 = wordToBool(and(bools, 0x1));
// shift `bools` by one and extract the right-most bit
let b1 = wordToBool(and(shr(1, bools), 0x1));
// shift `bools` by two and extract the right-most bit
let b2 = wordToBool(and(shr(2, bools), 0x1));
return fn(b0, b1, b2);
};
}Функция unpack_bools реализует пользовательскую декодировку ABI. Она является функцией высшего порядка, которая «оборачивает» входную функцию, принимающую три отдельных логических значения и возвращающую значение произвольного типа T, извлекая аргументы из трёх младших битов входного слова. Такой пример невозможно реализовать в классическом Solidity, даже с использованием модификаторов, поскольку они не могут изменять аргументы, передаваемые внутрь оборачиваемой функции.
Кроме того, поддерживается определение (некурсивных) анонимных функций с помощью ключевого слова lam. Функции, определённые таким образом, могут захватывать значения из области видимости, в которой они объявлены. В качестве примера рассмотрим вспомогательную функцию для тестирования, подсчитывающую количество вызовов произвольной функции:
forall T U . function count_calls(fn : (T) -> U) -> (memory(word), (T) -> U) {
let counter : memory(word) = allocate(32);
return (counter, lam (a : T) -> {
counter += 1;
return fn(a);
});
}Реализация здесь аналогична тому, как это сделано в системных языках, таких как Rust и C++: компилятор генерирует уникальный тип для каждой анонимной функции, содержащей захваченные значения, а эти уникальные типы становятся вызываемыми за счёт принадлежности к специальному типовому классу «вызываемых» (invokable), подобно трейту Fn в Rust. Такой подход обеспечивает высокую эффективность с точки зрения затрат газа во время выполнения.
Вывод типов
Core Solidity поддерживает вывод типов почти в любом контексте. Аннотации типов обычно требуются только тогда, когда это желательно для улучшения читаемости или понимания кода. Алгоритм вывода типов разрешим, а ситуации, в которых возникает неоднозначность и требуется явная аннотация, крайне ограничены. Благодаря этому удаётся избавиться от большого количества синтаксического шума, присущего классическому Solidity.
Например, присваивание выражения переменной в классическом Solidity нередко приводит к избыточным аннотациям, даже если типы уже присутствуют в самом выражении:
(bytes memory a, bytes memory b) = abi.decode(input, (bytes, bytes));
То же самое определение в Core Solidity выглядит значительно чище:
let (a, b) = abi.decode(input, (uint256, uint256));
Ещё одна частая причина раздражения при работе с классическим Solidity — синтаксический шум при определении литералов массивов. Рассмотрим следующий фрагмент:
uint256[3] memory a = [1, 2, 3];
Это объявление отвергается компилятором классического Solidity со следующей ошибкой:
Error: Type uint8[3] memory is not implicitly convertible to expected type uint256[3] memory.
Причина ошибки заключается в том, что классический Solidity реализует ограниченную и специализированную форму вывода типов для литералов массивов: тип элементов массива определяется как тип первого выражения в списке, к которому можно неявно привести все остальные элементы (в данном случае — uint8). Затем компилятор выдаёт ошибку несоответствия типов при попытке присвоить это значение переменной несовместимого типа.
❤4
Чтобы приведённое выше определение было принято, приходится добавлять неочевидное приведение типа к первому элементу массива:
Алгоритм вывода типов на основе ограничений в Core Solidity гораздо более общий и позволяет опустить такое приведение:
#core
uint256[3] memory a = [uint256(1), 2, 3];
Алгоритм вывода типов на основе ограничений в Core Solidity гораздо более общий и позволяет опустить такое приведение:
uint256[3] memory a = [1, 2, 3];
#core
👍4❤2
Погружение в Core Solidity. Часть 5
SAIL, десахаризация и стандартная библиотека
Помимо расширения поверхностного языка, переход на Core Solidity также введёт новый промежуточный язык среднего уровня, доступный пользователям: SAIL (Solidity Algebraic Intermediate Language — Алгебраический промежуточный язык Solidity). Это и есть «ядро» Core Solidity. SAIL представляет собой максимально упрощённый язык, на котором можно выразить всё разнообразие высокоуровневых конструкций, присутствующих в классической Solidity. Он состоит из следующих примитивных конструкций:
- Функции
- Контракты
- Блоки ассемблера (Yul)
- Объявление и присваивание переменных SAIL
- Выражение условного ветвления с коротким замыканием (if-then-else)
- Алгебраические типы данных и сопоставление с образцом
- Классы типов (type classes)
- Обобщения (generics)
Переменная SAIL концептуально похожа на переменную Yul: компилятор связывает её с ячейкой в стеке EVM. В SAIL существует единственный встроенный тип (word), диапазон значений которого совпадает с типами bytes32 или uint256 в классической Solidity и который семантически можно рассматривать как тип, соответствующий одному слоту стека EVM. Контракты в SAIL крайне низкоуровневы — по сути, это просто точки входа времени выполнения и инициализационного кода (initcode).
Хотя в текущей реализации SAIL используется Yul в качестве языка ассемблера, с теоретической точки зрения этот выбор в значительной степени произволен, и вместо него можно было бы использовать, например, ассемблер на основе RISC-V.
Мы уверены, что SAIL достаточно выразителен, чтобы реализовать все высокоуровневые функции и типы языка как комбинацию определений из стандартной библиотеки и проходов десахаризации — то есть синтаксических преобразований времени компиляции в примитивы SAIL. Core Solidity, таким образом, представляет собой SAIL, дополненный дополнительным «синтаксическим сахаром» и библиотеками. Он схож с Yul в своей двойной роли как промежуточного представления компилятора и низкоуровневого языка, доступного пользователю, и все примитивы SAIL будут непосредственно доступны при написании кода на Core Solidity. Подобный подход к построению языков широко применяется в других областях, требующих высокой надёжности (например, в системах автоматического доказательства теорем), и, по нашему мнению, он приносит существенные преимущества как для пользователей языка, так и для безопасности и корректности его реализации.
Мы ожидаем, что сможем создать исполняемую формальную семантику для SAIL. Это позволит нам математически гарантировать ключевые свойства системы типов Solidity, предоставить эталонную реализацию для дифференциального фаззинга, а также формально верифицировать как стандартную библиотеку, так и высокоуровневые языковые конструкции. Мы считаем, что это станет неотъемлемой частью нашей общей стратегии обеспечения корректности, особенно по мере роста сложности языка и масштабов систем, создаваемых с его помощью.
Авторы библиотек получат практически ту же выразительную мощность, что и разработчики языка, и смогут создавать абстракции, ощущающиеся как встроенные в сам язык («язык на основе библиотек»). Появится возможность определять и использовать альтернативные реализации стандартной библиотеки или полностью отключать стандартную библиотеку. При отключённой стандартной библиотеке можно будет писать код на Core Solidity с почти таким же уровнем контроля, как при использовании низкоуровневых ассемблерных языков вроде Yul или Huff, но при этом с современной и выразительной системой типов, основанной на математически строгих принципах.
SAIL, десахаризация и стандартная библиотека
Помимо расширения поверхностного языка, переход на Core Solidity также введёт новый промежуточный язык среднего уровня, доступный пользователям: SAIL (Solidity Algebraic Intermediate Language — Алгебраический промежуточный язык Solidity). Это и есть «ядро» Core Solidity. SAIL представляет собой максимально упрощённый язык, на котором можно выразить всё разнообразие высокоуровневых конструкций, присутствующих в классической Solidity. Он состоит из следующих примитивных конструкций:
- Функции
- Контракты
- Блоки ассемблера (Yul)
- Объявление и присваивание переменных SAIL
- Выражение условного ветвления с коротким замыканием (if-then-else)
- Алгебраические типы данных и сопоставление с образцом
- Классы типов (type classes)
- Обобщения (generics)
Переменная SAIL концептуально похожа на переменную Yul: компилятор связывает её с ячейкой в стеке EVM. В SAIL существует единственный встроенный тип (word), диапазон значений которого совпадает с типами bytes32 или uint256 в классической Solidity и который семантически можно рассматривать как тип, соответствующий одному слоту стека EVM. Контракты в SAIL крайне низкоуровневы — по сути, это просто точки входа времени выполнения и инициализационного кода (initcode).
Хотя в текущей реализации SAIL используется Yul в качестве языка ассемблера, с теоретической точки зрения этот выбор в значительной степени произволен, и вместо него можно было бы использовать, например, ассемблер на основе RISC-V.
Мы уверены, что SAIL достаточно выразителен, чтобы реализовать все высокоуровневые функции и типы языка как комбинацию определений из стандартной библиотеки и проходов десахаризации — то есть синтаксических преобразований времени компиляции в примитивы SAIL. Core Solidity, таким образом, представляет собой SAIL, дополненный дополнительным «синтаксическим сахаром» и библиотеками. Он схож с Yul в своей двойной роли как промежуточного представления компилятора и низкоуровневого языка, доступного пользователю, и все примитивы SAIL будут непосредственно доступны при написании кода на Core Solidity. Подобный подход к построению языков широко применяется в других областях, требующих высокой надёжности (например, в системах автоматического доказательства теорем), и, по нашему мнению, он приносит существенные преимущества как для пользователей языка, так и для безопасности и корректности его реализации.
Мы ожидаем, что сможем создать исполняемую формальную семантику для SAIL. Это позволит нам математически гарантировать ключевые свойства системы типов Solidity, предоставить эталонную реализацию для дифференциального фаззинга, а также формально верифицировать как стандартную библиотеку, так и высокоуровневые языковые конструкции. Мы считаем, что это станет неотъемлемой частью нашей общей стратегии обеспечения корректности, особенно по мере роста сложности языка и масштабов систем, создаваемых с его помощью.
Авторы библиотек получат практически ту же выразительную мощность, что и разработчики языка, и смогут создавать абстракции, ощущающиеся как встроенные в сам язык («язык на основе библиотек»). Появится возможность определять и использовать альтернативные реализации стандартной библиотеки или полностью отключать стандартную библиотеку. При отключённой стандартной библиотеке можно будет писать код на Core Solidity с почти таким же уровнем контроля, как при использовании низкоуровневых ассемблерных языков вроде Yul или Huff, но при этом с современной и выразительной системой типов, основанной на математически строгих принципах.
❤3