При разработке смарт-контрактов неправильный порядок наследования может привести к неожиданному поведению, особенно при работе с контролем разрешений и переопределением функций. Когда контракт наследуется от нескольких родительских контрактов, порядок выполнения конструкторов и правил покрытия функций становится особенно важным.
Предположим, у нас есть два контракта ParentA и ParentB, а также дочерний контракт Child, который наследуется от этих двух контрактов. Контракт ParentA содержит конструктор и функцию setOwner, а ParentB также определяет функцию setOwner, но ее функции отличаются. Наша цель — позволить контракту Child вызывать функцию setOwner ParentA, но неправильный порядок наследования приведет к вызову версии ParentB.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ParentA {
address public owner;
constructor() {
owner = msg.sender;
}
function setOwner(address newOwner) public {
owner = newOwner;
}
}
contract ParentB {
function setOwner(address newOwner) public {
// Реализация здесь отличается от ParentA, но нас не интересуют конкретные детали.
}
}
// Ненадлежащее исполнение приказа
contract Child is ParentB, ParentA {
// ...
}
В приведенном выше коде дочерний контракт наследует ParentB и ParentA. Однако в Solidity, если два родительских контракта определяют функции с одинаковым именем, порядок наследования определяет, какая функция будет переопределена первой. Следовательно, в контракте Child функция setOwner на самом деле является версией ParentB, а не той версией ParentA, которую мы ожидали.
Чтобы решить эту проблему, нам нужно настроить порядок наследования, чтобы гарантировать, что дочерний контракт может вызывать правильную функцию setOwner. В то же время, чтобы четко указать, какую родительскую функцию контракта мы хотим вызвать, мы можем использовать ключевое слово super, предоставляемое Solidity.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ParentA {
address public owner;
constructor() {
owner = msg.sender;
}
function setOwnerA(address newOwner) public {
owner = newOwner;
}
}
contract ParentB {
function setOwnerB(address newOwner) public {
// Реализация здесь отличается от ParentA.
}
}
// Правильный порядок нахождения
contract Child is ParentA, ParentB {
// Вызов функции setOwner объекта ParentA
function setOwner(address newOwner) public {
ParentA.setOwnerA(newOwner); // Явно вызовите setOwnerA для ParentA.
}
}
В этой модифицированной версии дочерний контракт сначала наследуется от ParentA, а это означает, что функции и переменные состояния ParentA будут инициализированы раньше, чем у ParentB. Кроме того, мы переименовали функции setOwner в ParentA и ParentB, чтобы избежать конфликтов имен, и определили новую функцию setOwner в контракте Child, которая явно вызывает функцию setOwnerA в ParentA.
Таким образом, мы гарантируем, что функция setOwner в контракте Child вызывает версию ParentA, избегая проблем с покрытием функций, вызванных неправильным порядком наследования.