Новая рабочая неделя, старт школьного сезона, а у меня небольшой отпуск. Просто хочу немного отдохнуть от контента, постоянной гонки со своим проектом и аудитами, поэтому чуть реже на неделе будут выходить посты.
Однако сегодня мы разберем сразу 3 пункта с репо Chinmaya! Два из них очень похожи, и один является продолжение другого. Звучать они как:
33. Events are inheritable. The Log and its event data is not accessible from within contracts (not even from the contract that created them.
34. it is possible to “fake” the signature (topic0) of another event using an anonymous event
35. Errors are inheritable. the revert data of inner calls is propagated back through the chain of external calls by default. Low-level calls do not throw an exception.
что в переводе:
33. События наследуются. Журнал и его данные о событиях недоступны изнутри контрактов (даже из контракта, который их создал).
34. можно «подделать» подпись (topic0) другого события, используя анонимное событие
35. Ошибки наследуются. Данные о возврате внутренних вызовов по умолчанию передаются обратно по цепочке внешних вызовов. Низкоуровневые вызовы не выбрасывают исключение.
Если мы уже немного знаем о Solidity, то уже должны понимать систему наследований. Банальный пример:
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {}
}
В этом контракте мы создаем токен с наследованием функционала от ERC20. В своих функциях мы можем использовать события, которые были установлены в ERC20 - Transfer и Approval. Если бы там были созданы error, то мы бы могли использовать и их в своем контракте токена.
Если мы говорим об ошибках (error) в контракте, то данные об них должны использоваться только для индикации сбоя, но не как средство управления потоком вызовов между контрактами. Причина в том, что данные о возврате внутренних вызовов по умолчанию распространяются обратно по цепочке внешних вызовов. Это означает, что внутренний вызов может «подделать» данные о return value, которые будут выглядеть так, как будто они могли прийти от вызвавшего его контракта.
Другими словами, если мы делаем вызов из контракта А в контракт В, который в свою очередь вызывает еще несколько контрактов, то вполне может возникнуть ситуация, что контракт В нарочно или нет, имеет такие же ошибки как другие контракты и возвращает информацию, что это в нем был сбой, а не в контрактах ниже.
На практике у меня такое никогда не встречалось, но возможность все таки есть.
С событиями (event) дела также обстоят интересно.
На уровне EVM нет такого понятия как Solidity events. Там есть всего 4 функции записи лога - LOG0, LOG1, LOG2, LOG3 и LOG4. Каждая функция LOG «регистрирует» некоторое количество байтов данных, индексированных от 0 до 4 bytes32 «topic» (те самые поля idexed в событиях). Когда вы создаете «событие» в Solidity, неиндексированные аргументы события кодируются через ABI в байты и передаются как аргумент данных в LOG, а индексированные события передаются как темы.
Однако Solidity также вставляет «подпись события», хэшированную, как дополнительную (первую) тему (чтобы было легко искать все события, например, ERC-20 Transfer(address,uint256,uint256)). Это происходит, если событие не помечено как анонимное, в противном случае он ничего не вставляет.
Таким образом, вывод следующих двух emit на блокчейн неразличим:
pragma solidity 0.8.20;
contract Test {
event Value(uint256 value);
event NameDoesntMatter(bytes32 indexed topic0, uint256 value) anonymous;
function test() external {
emit Value(123);
emit NameDoesntMatter(keccak256(bytes("Value(uint256)")), 123);
}
}