Ethereum学习笔记 ---- 多重继承中的 C3线性化算法

学习 solidity 合约多重继承时,官方文档介绍 solidity 采用 C3线性化算法 来确定多重依赖中的继承顺序。
维基百科上有很好的说明:
C3线性化
C3 linearization

下面通过实验来深入理解一下。

举个反例

在 c3_notok.py 中定义如下类

class Type(type):
    def __repr__(cls):
        # Will make it so that repr(O) is "O" instead of "<__main__.O>",
        # and the same for any subclasses of O.
        return cls.__name__

class O(object, metaclass=Type): pass

class A(O): pass

class X(O, A): pass

if __name__ == '__main__':
    print(X.mro())

执行会 throw error:

$ python3 c3_notok.py

TypeError: Cannot create a consistent method resolution
order (MRO) for bases O, A

分析错误原因

根据 C3 线性化算法,计算 X 的 mro (Method Resolution Order):

L(O) := [O]

L(A) := [A] + merge(L(O), [O])
     := [A] + merge([O], [O])
     := [A, O]

L(X) := [X] + merge(L[O], L[A], [O, A])
     := [X] + merge([O], [A, O], [O, A])

可以看到在最后一步计算中, OA[A, O], [O, A] 两个列表中分别为 head 和 tail,算法无法继续进行下去,所以 class X(O, A) 的定义不合法。

举个正例

在 c3_ok.py 中定义如下

class Type(type):
    def __repr__(cls):
        # Will make it so that repr(O) is "O" instead of "<__main__.O>",
        # and the same for any subclasses of O.
        return cls.__name__

class O(object, metaclass=Type): pass

class A(O): pass

class X(A, O): pass

if __name__ == '__main__':
    print(X.mro())

执行脚本,可以看到正常的输出结果:

$ python3 c3_ok.py 
[X, A, O, <class 'object'>]

分析

根据 C3 线性化算法,计算 X 的 mro:

L(O) := [O]

L(A) := [A] + merge(L(O), [O])
     := [A] + merge([O], [O])
     := [A, O]

L(X) := [X] + merge(L[A], L[O], [A, O])
     := [X] + merge([A, O], [O], [A, O]) 
     := [X, A] + merge([O], [O], [O])
     := [X, A, O]

solidity 中的多重继承

需要注意的是,在 solidity 中定义合约时,如果是多重继承,parent contracts 的顺序跟 python 中的 parent classes 的顺序是相反的,即 farthest parent contract 应该书写得离当前定义的合约最近,如下:

contract O {}

contract A is O {}

contract X is O, A {}

多重继承合约的 storage layout

remix 中编写如下合约

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.0 <0.9.0;

import "hardhat/console.sol";

struct UintStruct {
    uint value;
}

contract O {
    uint v0 = 0;
}

contract A is O {
    uint v1= 1;
}

contract B is O {
    uint v2= 2;
}

contract C is O {
    uint v3= 3;
}

contract D is O {
    uint v4= 4;
}

contract X1 is O, A, B, C, D {
    uint vx1 = 6;

    function showStorageInX1() public view {
        UintStruct storage us;
        uint i;
        for (; i < 6; ++i) {
            assembly {
                us.slot := i
            }
            console.log(i, us.value);
        }
    }
}

contract X2 is A, B, C, D {
    uint vx2 = 7;

    function showStorageInX2() public view {
        UintStruct storage us;
        uint i;
        for (; i < 6; ++i) {
            assembly {
                us.slot := i
            }
            console.log(i, us.value);
        }
    }
}

可以看到, OABCD 合约的共同 parent contract,而 X2X1 少继承了一个 O

部署两个合约,分别调用两个合约中的函数 showStorageInX1()showStorageInX2(),结果如下:

可以看到,X1 和 X2 的 storage layout 是一样的,所以,在多重继承定义中,其实可以省略公共 parent contract。

posted on 2024-09-12 16:29  HorseShoe2016  阅读(38)  评论(0编辑  收藏  举报