А сегодня у нас редко изучаемая тема на курсах и при самостоятельном прохождении: порядок наследования. Об этом, в основном, просто упоминают, но не особо рассказывают почему это работает так.
И в этом нам поможет следующий пункт:
41. The order in which the base classes are given in the is directive is important: You have to list the direct base contracts in the order from “most base-like” to “most derived”
что в переводе:
Порядок указания базовых классов в директиве is очень важен: вы должны перечислить прямые базовые контракты в порядке от «наиболее похожих на базовые» до «наиболее производных».
Вообще, я сначала не очень понял этот пункт и, как обычно, полез в документацию. Давайте разбираться вместе.
Языки, допускающие множественное наследование, сталкиваются с несколькими проблемами. Одна из них - Diamond Problem. Solidity похож на Python тем, что использует «C3-линеаризацию» для принудительного установления определенного порядка в направленном ациклическом графе (DAG) базовых классов (чтобы это не значило). Это приводит к желаемому свойству монотонности, но не позволяет использовать некоторые графы наследования.
Другой более простой способ объяснить это заключается в том, что при вызове функции, которая определена несколько раз в разных контрактах, базовые данные перебираются справа налево (в Python - слева направо) в порядке глубины поиска, останавливаясь на первом совпадении. Если контракт с базой уже был просмотрен, он пропускается.
В следующем коде Solidity выдаст ошибку «Linearization of inheritance graph impossible».
pragma solidity >=0.4.0 <0.9.0;
contract X {}
contract A is X {}
// This will not compile
contract C is A, X {}
Причина в том, что C просит X отменить A (указывая A, X в таком порядке), но A сама просит отменить X, что является противоречием, которое невозможно разрешить.
Из-за того, что вам придется явно переопределять функцию, которая наследуется от нескольких баз без уникального переопределения, линеаризация C3 не слишком важна на практике.
Одна из областей, где линеаризация наследования особенно важна и, возможно, не так очевидна, - это когда в иерархии наследования есть несколько конструкторов. Конструкторы всегда будут выполняться в линеаризованном порядке, независимо от того, в каком порядке их аргументы предоставляются в конструкторе наследуемого контракта. Например:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Base1 {
constructor() {}
}
contract Base2 {
constructor() {}
}
// Constructors are executed in the following order:
// 1 - Base1
// 2 - Base2
// 3 - Derived1
contract Derived1 is Base1, Base2 {
constructor() Base1() Base2() {}
}
// Constructors are executed in the following order:
// 1 - Base2
// 2 - Base1
// 3 - Derived2
contract Derived2 is Base2, Base1 {
constructor() Base2() Base1() {}
}
// Constructors are still executed in the following order:
// 1 - Base2
// 2 - Base1
// 3 - Derived3
contract Derived3 is Base2, Base1 {
constructor() Base1() Base2() {}
}
Теперь вы знаете чуть больше о линеаризация наследования... Или нет?
#inheritance