在一些博客或者文档中我们可能看到对solidity中super关键字的描述是“用于访问(最近的)父合约的一些函数或者状态变量”
这里有一个简单的具象化但不全面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| //定义一个基类合约 contract A{ string public str01 = "我是A";
function foo() public view virtual returns(string memory){ return str01; } }
//A基类合约的子合约(派生合约) contract B is A{ string public str01 = "我是B";
function foo() public view override returns(string memory){ super.foo(); } }
|
编译部署B合约,当我们在用外部调用的时候B.foo()函数的时候,但从关键字的理解上来说,super代表最近的父合约,那么这里的父合约就只有A合约,所以会调用A中的foo()方法;
但是当遇到稍微复杂一些的多线继承的时候(多线继承推荐看登链社区的solidity中文的官方文档https://learnblockchain.cn/docs/solidity/contracts.html#index-17
),比如类似的钻石问题,我们就需要搞清楚这个时候的合约编译以及运行情况,这样才能更好的理清思路.
这里仍然有一个简单的具象化的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.13;
/* 继承树: God / \ Adam Eve \ / people */
contract God { event Log(string message);
function foo() public virtual { emit Log("God.foo called"); }
function bar() public virtual { emit Log("God.bar called"); } }
contract Adam is God { function foo() public virtual override { emit Log("Adam.foo called"); Adam.foo(); }
function bar() public virtual override { emit Log("Adam.bar called"); super.bar(); } }
contract Eve is God { function foo() public virtual override { emit Log("Eve.foo called"); Eve.foo(); }
function bar() public virtual override { emit Log("Eve.bar called"); super.bar(); } }
contract people is Adam, Eve { function foo() public override(Adam, Eve) { super.foo(); }
function bar() public override(Adam, Eve) { super.bar(); } }
//代码来自:https://github.com/AmazingAng/WTF-Solidity/blob/main/13_Inheritance/readme.md
|
先分析一下这个钻石问题的多线继承关系和合约内容:
- God合约做为基类合约有两个待重写的方法–bar()和foo()
- 当God合约的函数被调用时会触发God的Log事件
- Adam和Eve做为God的派生合约都继承了God合约并且都重写了待重写方法-foo()和bar方法
- 同时,调用时会触发各自的事件peole继承foo()和bar()合约,作为其子类重写两个方法
上面合约编译后部署people合约,调用bar()函数,出现结果:
上面合约编译后部署people合约,调用foo()函数,出现结果:
如果你觉得这个运行结果在你的预期之内是一模一样的,那么说明你已经考虑到了:
1 2
| 1. 多线继承时合约的编译顺序是如何的 2. super关键字的的“最近”是紧紧跟上编译顺序的,而不是简单的父合约
|
总之,强调一个问题,在多线继承的时候一定要注意继承时合约的编译顺序(与python相反,一些文章中可以读到不详细了解),并且进一步需要理解的是solidity中合约的编译顺序不是EVM内部决定的,而是依靠我们书写的顺序人为安排的
那么带着这个结论和对函数调用出现的结果的疑问,先看调用bar函数(依照步骤理解):
1 2 3 4 5 6
| 1. 明白编译顺序在编译people合约的时候,people合约继承了两个合约(Aam和Eve),这两个合约都是God的派生合约,那么,编译顺序就是从基类向下:God-->Adam-->Eve-->people(最后是本合约),或者我们可以改变Eve和Adam的书写顺序``contract people is Adam, Eve``,那么编译顺序就变成了God->Eve-->Eve-->people 2. 调用bar()函数:执行super.bar,此时依照编译顺序,“最近的父合约”应该是Eve,所以执行Eve.bar()函数 3. Eve.bar()函数首先触发事件,然后调用super.bar(),这时依照编译顺序,“最近的父合约”变为Adam,所以执行Adam.bar()函数 4. Adam,bar()函数首先触发事件,然后调用super.bar(),这时依照编译顺序,“最近的父合约”变为基类合约--God合约,执行God.bar() 5.God.bar()触发事件 所以最后,按次序的三个事件被触发
|
同样的依照多线继承的合约编译顺序和super关键字的“最近的父合约”的思路,调用foo()函数,根据代码同样可以知道运行结果是会先触发Eve事件,然后触发Adam事件。
总结:
这样理解或许是比较的冗余,但是 不乏是为了理解原理和执行方式,并且很多官方文档并没有对此说明,在没有过多的学过c++或者python的多继承时,希望这篇文章会对你有帮助!