Solidity’s Inheritance နဲ့ သိထားသင့်သမျှ

Justin Thein Kyaw
6 min readSep 22, 2023

Solidity က တခြား OOP programming တွေလိုပဲ Inheritance feature ကို လုပ်ခွင့်ပေးထားပါတယ် Inheritance လုပ်တဲ့ keyword က is ဖြစ်ပါတယ်။ Multiple contract inheritance လုပ်တဲ့အခါ , ကိုအသုံးပြုပါတယ်။

Contract D is B, C {}

Parent Contract — Inheritance လုပ်ခံရတဲ့ Contract ကို base contract လို့ ခေါ်ပီး ၊ Inheritance လုပ်တဲ့ contract ကို derived contract လို့ခေါ်ပါတယ်။

Solidity မှာ Inheritance ကို Form 3 မျိုးနဲ့မြင်နိုင်ပါတယ်

  • Single Inheritance
  • Multiple-Level Inheritance
  • Hierarchical (hai·ur·aar·kuh·kl) Inheritance (Multi-Inheritance)

Function call ခေါ်တဲ့နေရာမှာ လက်ရှိ Contract ကနေ base contract ထဲက function ကိုလှမ်းခေါ်တဲ့အခါ Contract name နဲ့ explicitly ContractName.functionName သို့မဟုတ် super.functionName() နဲ့လှမ်းခေါ်လို့ရပါတယ်။

Contract တစ်ခုက တခြား Contract ဆီကနေ Inheritance လုပ်ထားပြီး derived contract ကို Blockchain ပေါ်ကို deploy လုပ်တဲ့အခါ Blockchain ပေါ်မှာ contract တစ်ခုတည်းပဲ သွားပြီး deploy လုပ်မှာဖြစ်ပါတယ်။ အဲအပြင် derived contract ကို compiled လုပ်လိုက်တဲ့အခါ derived contract + all base contract တွေအကုန်ကို contract တစ်ခုအနေနဲ့ compile လုပ်မှာဖြစ်ပါတယ်။

ဒီသဘောက derived contract မှတဆင့် base contract ကို function call ခေါ်တဲ့အခါ Blockchain ရဲ့ Internal message call အနေနဲ့ခေါ်သွားမှာမဟုတ်ပဲ JUMP instruction ကိုသုံးပြီး function ခေါ်သွားမှာပဲဖြစ်ပါတယ်။

မြန်မာလိုနားလည်အောင်ပြောရရင် contract ကတစ်ခုတည်းအနေနဲ့ပဲ EVM ကသိမှာပါ

Inheritance လုပ်လိုက်ရင် Base contract မှာ declare လုပ်ထားတဲ့ state , function တွေကို visibility modifier တွေရဲ့ Rule အတိုင်း access လုပ်ပိုင်ခွင့်၊ override လုပ်ပိုင်ခွင့် ကွဲပြားသွားမှာဖြစ်ပါတယ်။

Function overriding

တချို့ Programming Language တွေနဲ့မတူပဲ Solidity မှာ Base Contract က သူ့မှာ ပါတဲ့ Function တွေကို derived contract က modify လုပ်ပိုင်ခွင့်ပေးချင်ရင် Function တိုင်းမှာ virtual ဆိုတဲ့ Keywords ကို သုံးပေးရမှာဖြစ်ပါတယ်။

အကယ်လို့ Base contract ထဲက Function မှာ vitural ဆိုတဲ့ Keyword မပါပဲ derived contract ကနေ ပြင်ဆင်ရင် Compile error တက်မှာဖြစ်ပါတယ်။

အောက်ပါ ပုံအတိုင်း Trying to override non-vitrual function ဆိုတဲ့ TypeError ကိုလာပြမှာဖြစ်ပါတယ်။ ကိုယ်သုံးတဲ့ IDE ပေါ်မူတည်ပြီး ဖော်ပြပါ ပုံနဲ့ ကွဲလွဲနိုင်ပါတယ်။ ဒါပေမယ့် error description ကတော့ တူတူပါပဲ

ထို့အတူ ပဲ Derived contract ကနေ Base contract ရဲ့ function ကို override လုပ်ချင်တဲ့အခါ override ဆိုတဲ့ keyword ကို ရေးပေးရပါတယ်။ နမူနာ contract တွေ ကိုအောက် section တွေမှာ ကြည့်ပါ။

Solidity version 0.6.0 နဲ့ သူ့အရင်က version တွေမှာက implicit override တဲ့နည်းရှိပါတယ်။ အဲတာက Base class ထဲကအတိုင်း Function name နဲ့ parameters တွေကိုတူအောင်ရေးထားရင် compiler က ဒါကို override လုပ်တယ်သတ်မှတ်ပီး Base contract မှာသွားစစ်ပီး မရှိရင် error ပြပါတယ်

version 0.6.0 အထက်မှာက virtual နဲ့ override keyword တွေကို သုံးကိုသုံးရပါတယ်။ အဲအတွက် compiler လာကလည်း additional compile time ယူပီးသွားစစ်ပေးပါတယ်။ Implicit ကလို function ရှိမရှိစစ်တာထက်ပိုပါတယ်။

Multiple inheritance လုပ်ရင် override ဆိုတဲ့ modifier ထဲမှာ Base contract တွေထည့်ပေးရပါတယ်။ ဥပမာ override(ContractA, ContractB)

ဒါပေမယ့် အဲမှာပါတဲ့ Contract A နဲ့ Contract B ဆိုတာ order မရှိပါဘူး။ အဆင်ပြေသလိုရေးလို့ရပါတယ်။ ဘယ်လိုအခြေအနေမှာရေးရလဲဆိုရင် Base contract ၂ ခုလုံးမှာ function name တူ parameter တူ ပါလာတဲ့အခါမျိုးကျ ရေးပေးရပါတယ်။

ဘယ် Contract ထဲက ဟာကို ခေါ်မလဲဆိုတာက super.function() လို့ခေါ်တဲ့အချိန်မှ ဆုံးဖြတ်ပေးပါတယ်။

Visibility and Mutability

Function overriding မှာ နည်းနည်း အခရာကျတာက Function ရဲ့ visibility နဲ့ Mutability ပါ။ Mutability ကိုစပြောရအောင်။

💡 Solidity ရဲ့ Mutability မှာ pure, view, payable နဲ့ nonpayable ဆိုပြီး ၄ ခုရှိပါတယ်။

  • Base contract မှာပါတဲ့ Function ရဲ့ view ကို pure mutability နဲ့ override လုပ်လို့ရပါတယ်။
  • Base contract မှာပါတဲ့ Function ရဲ့ nonpayable ဆိုတဲ့ mutability ကို view သို့ pure mutability နဲ့ override လုပ်လို့ရပါတယ်။
  • payable ဆိုတဲ့ Modifier ကတော့ exception ပါ။ ဘယ် Mutability နဲ့မှ override လုပ်လို့မရပါဘူး။

Visibility အရဆိုရင် သူ့ rule အတိုင်းပဲအလုပ်လုပ်ပါတယ်။

  • Internal (YES): Base contract မှာပါတဲ့ Function က internal လို့ သတ်မှတ်ထားရင် Base contract နဲ့ သူ့ကို inherit လုပ်ထားတဲ့ derived contract တွေကသာခေါ်လို့ရပါမယ်။
  • External (NO): Derived contract တွေကကော တခြား internal function တွေကကောခေါ်လို့မရပါဘူး။ Blockchain network အပြင်ဘက်က call တွေကသာလျှင် ဒီ Function ကိုခေါ်နိုင်မှာဖြစ်ပါတယ်။
  • Private (NO): Base contract မှာပါတဲ့ Function တွေကလွဲလို့ တခြား derived contract ကနေ မခေါ်နိုင်ပါဘူး။
  • Public (YES): အကုန်လုံးကခေါ်နိုင်ပါတယ်။

ဒီမှာသတိထားစရာတစ်ချက်ရှိပါတယ်။ ပုံမှန်ဆို External သတ်မှတ်ထားတဲ့ function ကို derived တွေက access လုပ်လို့မရပေမယ့် base contract မှာသတ်မှတ်ထားတဲ့ function ရဲ့ parameters နဲ့ return type တွေကတူနေရင် Derived contract ရဲ့ Public variable နဲ့ override လုပ်လို့ရပါတယ်။

For example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

contract A
{
function f() external view virtual returns(uint) { return 5; }
}
contract B is A
{
uint public override f;
}

Inheritance Order (MRO — Method Resolution Order)

နောက်သတိထားရမယ့်အချက်က Inheritance order ပါ။ ဘာလို့ ဆိုရင် Solidity မှာက Hierarchical (Multiple Inheritance) ကိုခွင့်ပြုထားလို့ Diamond problem လို ပြဿနာတွေ တက်နိုင်ချေရှိပါတယ်။

ဥပမာပြောရရင် Contract တစ်ခုက တခြား Contract ၂ ခု ဆီကနေ Inheritance လုပ်ထားတဲ့အခါမျိုးမှာ base contract ၂ခုလုံး ထဲမှာပါတဲ့ နာမည်တူ(Structure — Function signature တူတဲ့) function ကိုခေါ်သုံးတဲ့အခြေအနေမျိုးရောက်သွားတဲ့အခါ Ambiguous ဖြစ်နိုင်တဲ့ အနေအထားမို့ Inheritance order ကို သိထားရပါ့မယ်။

ဒါမျိုးကို Diamond Problem လို့လဲခေါ်ပါတယ်။

Java လို Programming Language မျိုးမှာ Multiple Inheritance လုပ်ခွင့်မပေးထားပဲ Multi-Level Inheritance သာလုပ်ခွင့်ပေးထားလို့ Java လို background ကလာတဲ့လူဆို အောက်ကစာပိုဒ်ကို သေချာဖတ်ပေးစေချင်ပါတယ်။

အပေါ်က ပုံ အရ Base contract ဖြစ်တဲ့ A ကို B နဲ့ C က Inheritance လုပ်ထားတယ်။ ကျန်တဲ့ Contract F ကတော့ ‘Multi-layer inheritance’ (မြင်နေကျပုံစံ) မို့ ခနထားလိုက်မယ်။

ထိုနည်းတူ Contract D က B, C ကို Inheritance လုပ်ထားတယ်။ ဒါဆို Contract D က God class ဖြစ်သွားပီ။ ဒီတော့ ကျတော် Highlight လုပ်ချင်တာက Contract D ဖြစ်လို့ Contract D အကြောင်းပဲပြောမယ်။ Implementation အကြမ်းဖျဥ်း ပုံဖော်ရရင် အောက်ပါ code section အတိုင်းမြင်ရပါမယ်။

contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}

contract B is A {
function foo() public pure override returns (string memory) {
return "B";
}
}

contract C is A {
function foo() public pure override returns (string memory) {
return "C";
}
}

contract D is B, C {
function foo() public pure override(B, C) returns (string memory) {
return super.foo();
}
}

ဆိုတော့ Contract D မှာ ပါတဲ့ foo function ကိုခေါ်ရင် ဘာကို return ပြန်မယ်လို့ထင်ပါသလဲ။ “B” လား “C” လား။ B ပဲပြန်ပြန် C ပဲ ပြန်ပြန် ဘာလို့ပြန်ရတာလဲ?

💡 အဖြေကတော့ C ကို return ပြန်မှာဖြစ်ပါတယ်။ Why?

Solidity ရဲ့နောက်ကွယ်က အလုပ်လုပ်တဲ့ MRO ကြောင့်ပါ။ ဒါကြောင့် solidity မှာ Inheritance order က အရေးကြီးပါတယ်။ Developer အနေနဲ့ inheritance order ကို ‘most base-like’ to ‘most derived’ ပုံစံ order စီပေးရပါမယ်။

ဘာကို ပြောချင်တာလဲဆိုတော့ ‘base contract (parent — root) နဲ့ နီးတဲ့ Contract ကို ‘most base-like’ လို့ သတ်မှတ်ပီး Derived contract နဲ့နီးတဲ့ Contract ကို ‘most derived’ လို့သတ်မှတ်ပါတယ်။

ပိုပီး မြင်သာအောင် အောက် က ပုံ-၂ ကို တချက်ကြည့်ပါ။

အထက်ပါ ပုံအရ Contract D ကနေ Inheritance လုပ်တဲ့အခါ အောက်ပါအတိုင်း ‘most base-like’ to ‘most derived’ ပုံစံစီရမှာဖြစ်တယ်

Contract D is B, C

ဒီလို Order ကို most base-like to most derived ပုံစံ Order စီပေးခြင်းအားဖြင့် ဘယ်သူက Base class နဲ့နီးပြီး ဘယ်သူကတော့ derived နဲ့နီးတယ်ဆိုတဲ့ သတ်မှတ်ချက်ထွက်လာမှာဖြစ်ပါတယ်။ အဲတာမှသာ Solidity က ‘C3 Linearization’ algorithms ကိုသုံးပြီး DAG ပုံကိုဖန်တီးနိုင်မှာဖြစ်ပါတယ်။

နောက် မေးခွန်းတစ်ခု ထပ်ပေါ်လာပါတယ်။ အဲတာက ပုံ-၁ အရဆိုရင် Contract B ကော Contract C ကောက ‘most derived’ contract တွေပါ။ ဘာလို့ B ကို ရှေ့ထားပီး C ကို နောက်မှာ ထားရတာလဲ?

အဖြေက — ‘Developer have to list the parent contracts in the order from “most base-like” to “most derived’ လုပ်ရမယ်ဆိုတာက အဲလိုလုပ်မှသာလျှင် Solidity က C3 Linearization ကိုသုံးပီး Function order စီတဲ့အခါ တိကျတဲ့ DAG ထွက်မှာဖြစ်ပါတယ်။

most base like ကို ရှေ့ကမထားပဲ နောက်မှာ ထားချင်သပါ့ ဆိုလဲ ထားလို့ရပါတယ်။ Compiler ကော EVM ကော က ခွင့်မပြုထားတဲ့ inheritance graphs မဟုတ်မချင်း ဘာ error မှ တက်မှာမဟုတ်ပါဘူး။

ဥပမာ — အောက်က code ကို run ရင် solidity က Linearization of inheritance graph impossible ဆိုတဲ့ error တက်မှာဖြစ်ပါတယ်။ သူ့ရဲ့ Inheritance graphs ကိုမျက်လုံးထဲဖော်ကြည့်ပါ။ ဘာလို့ ဒီလို Graph မျိုးကိုအလုပ်မလုပ်သလဲ ဆိုတာ ကျတော် Solidity’s c3 linearlization ဆိုတဲ့ခေါင်းစဥ်နဲ့ စာရေးပေးပါ့မယ်။

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract X {}
contract A is X {}
// This will not compile
contract C is A, X {}

မြန်မာလိုပြောရရင် “Compiler အလုပ်လုပ် ပုံ အရ နောက်ဆုံးက ထည့်ပေးတဲ့ Contract ကို most derived contract လို့ယူဆပြီး အဲ contract ရဲ့ state တွေ Function တွေကို Inheritance လုပ်ပေးသွားမှာဖြစ်ပါတယ်။”

How does linearization Order work in solidity?

Hierarchical Inheritance ရဲ့ Structure က Tree data Structure နဲ့ဆင်ပါတယ်။ Tree data structure တွေမှာ node တခုချင်းစီကို traverse လုပ်ရင်သုံးတဲ့ `depth-first’ manner ဆိုတာရှိပါတယ်။ ဘယ်လိုအလုပ်လုပ်လဲဆိုတာတော့ ဒီမှာ ရှင်းပြမှာမဟုတ်ပါဘူး။ ရှာဖတ်ဖို့အကြံပြုချင်ပါတယ်။

Solidity မှာက ဘယ် Contract ဆီကနေ Inheritance လုပ်ရမလဲဆိုတာကို Depth-first order of inheritance (တနည်းအားဖြင့် C3 Linearization) ကိုသုံးပီး ဘယ် function တွေ state တွေကို Inheritance လုပ်မလဲဆုံးဖြတ်ပါတယ်။

💡 Linearization order ကိုသုံးခြင်းအားဖြင့် ဘယ် function, ဘယ် state variable တွေကို inheritance လုပ်ရမလဲမသိဘူးဆိုတဲ့ ambiguities ကို ရှောင်ရှားနိုင်ပါတယ်။

Linearization ကို solidity မှာမှမဟုတ်ပါဘူး တခြား Language တွေမှာလဲသုံးပါတယ်။ ဥပမာ — Python, Dylan, Perl, Ruby, Scala, Common Lisa

ဒီနေရာမှာသတိထားရမှာက C3 Linearlization Algorithms က Python နဲ့ပြောင်းပြန်ပါ။

Python မှာက Left → Right အလုပ်လုပ်ပြီး solidity မှာက Right → left အလုပ်လုပ်ပါတယ်။ Reverse linearization လို့လဲခေါ်ပါတယ်။

ကျတော် ဒီ Article နဲ့ပတ်သတ်ပြီး ဖြည့်စရာကျန်ပါသေးတယ် အဲတာက constructor တွေအကြောင်းပါ။

ဒါကတော့သိပ်ပြီးမခက်တဲ့အတွက် ကျတော်ထည့်မရေးပေးတော့ပါဘူး။

💡 ကျတော်အချိန်ရရင် ဒီ Article မှာပဲ Constructor အကြောင်း Update လုပ်ပေးပါ့မယ်

ခုလောက်ဆိုရင် Solidity မှာပါတဲ့ Inheritance အကြောင်းကို အပေါ်ယံနဲ့ နည်းနည်း Detail ကျကျသိပြီလို့ယုံပါတယ်။ ဒီ Article မှာပါတဲ့ C3 Linearlization အကြောင်းကိုသပ်သပ်ထပ်ရှင်းပါဦးမယ်။

Inheritance နဲ့ပတ်သတ်ပြီး ဆွေးနွေးပေးတဲ့ Brother Aye Chan Aung Thwin ကိုလည်း ဒီကနေကျေးဇူးတင်ကြောင်းပြောလိုပါတယ်ဗျ

Thanks and peace.

Ref:

https://ethereum.stackexchange.com/questions/114358/are-the-order-of-contract-names-inside-override-important

https://ethereum.stackexchange.com/questions/130845/how-does-solidity-implement-c3-linearization

https://ethereum.stackexchange.com/questions/78572/what-are-the-virtual-and-override-keywords-in-solidity

https://www.fromkk.com/posts/c3-linearization-and-python-mro--method-resolution-order/

--

--