Estaba jugando en remix solo para concretar mi comprensión de la linealización de Solidity, y obtuve un resultado que no esperaba y no puedo racionalizar. ¿Por qué AC_BA.go()
regresa 3
?
(Por favor, desplácese hasta la parte inferior del código a continuación).
pragma solidity ^0.4.24;
contract A {
function go() public pure returns (uint out) {
out = 1;
}
}
contract B {
function go() public pure returns (uint out) {
out = 2;
}
}
contract C {
function go() public pure returns (uint out) {
out = 3;
}
}
// returns 2 as expected
contract AB is A, B {
function go() public pure returns (uint out) {
out = super.go();
}
}
// returns 1 as expected
contract BA is B, A {
function go() public pure returns (uint out) {
out = super.go();
}
}
// returns 3 as expected
contract AC is A, C {
function go() public pure returns (uint out) {
out = super.go();
}
}
// refuses to compile, can't linearize, ok
//
// contact AC_A is AC, A {
// function go() public pure returns (uint out) {
// out = super.go();
// }
// }
// compiles, returns 2, from AB
contract AC_AB is AC, AB {
function go() public pure returns (uint out) {
out = super.go();
}
}
// compiles, returns 3, why???
contract AC_BA is AC, BA {
function go() public pure returns (uint out) {
out = super.go();
}
}
Tomando prestada la notación de la linealización C3 en Wikipedia , y teniendo en cuenta que Solidity invierte el orden típico ("Tiene que enumerar los contratos base directos en el orden de 'más base' a 'más derivado'. Tenga en cuenta que este orden es diferente del que se usa en Python").
L(AC) := [AC] + merge(L(C), L(A), [C, A])
= [AC] + merge([C], [A], [C, A])
= [AC, C] + merge([A], [A])
= [AC, C, A]
L(BA) := [BA] + merge(L(A), L(B), [A, B])
= [BA] + merge([A], [B], [A, B])
= [BA, A] + merge([B], [B])
= [BA, A, B]
L(AC_BA) := [AC_BA] + merge(L(BA), L(AC), [BA, AC])
= [AC_BA] + merge([BA, A, B], [AC, C, A], [BA, AC])
= [AC_BA, BA] + merge([A, B], [AC, C, A], [AC])
= [AC_BA, BA, AC] + merge([A, B], [C, A])
= [AC_BA, BA, AC, C] + merge([A, B], [A])
= [AC_BA, BA, AC, C, A] + merge([B])
= [AC_BA, BA, AC, C, A, B]
Entonces llamar AC_BA.go()
termina llamando C.go()
, que devuelve 3. No tengo una explicación intuitiva para ti; este es solo el comportamiento del algoritmo de linealización C3, que sigue Solidity.
EDITAR
El compilador de Solidity puede exportar un AST que incluye la linealización de contratos base. Un poco de Python puede convertirlo en una forma legible:
import json
import sys
symbol_map = {}
for source in json.load(sys.stdin)['sources'].values():
for symbol, ids in source['AST']['attributes']['exportedSymbols'].items():
for id in ids:
symbol_map[id] = symbol
for child in source['AST']['children']:
attributes = child['attributes']
if attributes.get('contractKind', None) == 'contract':
print('{}: {}'.format(attributes['name'], ' -> '.join(symbol_map[id] for id in attributes['linearizedBaseContracts'])))
Cómo ejecutarlo:
solc --combined-json ast test.sol | python3 linearization.py
Producción:
A: A
B: B
C: C
AB: AB -> B -> A
BA: BA -> A -> B
AC: AC -> C -> A
AC_AB: AC_AB -> AB -> B -> AC -> C -> A
AC_BA: AC_BA -> BA -> AC -> C -> A -> B
Cuando llamas AC_BA.go()
, ese llama BA.go()
, que a su vez llama AC.go()
y finalmente C.go()
(devuelve 3).
EDITAR 2
Surya es una herramienta útil que mostrará la herencia de contratos en orden lineal.
$ surya dependencies AC_BA test.sol
AC_BA
↖ BA
↖ AC
↖ C
↖ A
↖ B
Steve Waldman
usuario19510
Steve Waldman