Un rompecabezas de linealización de solidez

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();
    }
}

Respuestas (1)

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_BABAAC
  ↖ C
  ↖ A
  ↖ B
Dados los costos potenciales de malinterpretar la linealización de los contratos y los resultados no tan intuitivos, sería bueno que solc emitiera la linealización que establece. ¿Hay alguna manera de conseguir eso?
Edité mi respuesta. La primera parte estaba realmente mal (olvidé que Solidity invierte el orden típico de las clases base), y agregué la segunda parte que muestra cómo hacer que el compilador de Solidity le diga la linealización.
¡Gracias! (Eso tiene mucho más sentido, estaba un poco confundido acerca del pedido anterior, pero pensé que estaba en mí descifrarlo). Probaré con Surya.