The contract wizard often creates the statement "// The following functions are overrides required by Solidity" followed by a series of overrides that appear trivial. Why is this done? What happens without these override statements?
They are trivial. Unfortunately Solidity requires this manual overriding. If you remove them, the contract will not compile.
Ah, yes i see. but this code will compile
contract MyToken is ERC20Votes {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}
}
and this one will not
contract MyToken is ERC20, ERC20Permit, ERC20Votes {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}
}
is there any reason why we must specify "ERC20" and "ERC20Permit" even though ERC20Votes is inheriting from them? my guess is that we specify those for clarity, but I want to make sure leaving them out doesn't effect the actual function of the contract.
is there any reason why we must specify "ERC20" and "ERC20Permit" even though ERC20Votes is inheriting from them? my guess is that we specify those for clarity, but I want to make sure leaving them out doesn't effect the actual function of the contract.
From the language perspective - no. Inheritance in Solidity works like virtual inheritance in C++. If a contract appears more than once in the hierarchy, you still get only one copy of it. If you remove the ERC20
and ERC20Permit
from the inheritance list you'll get the same exact bytecode.
You can very easily verify it yourself. Put the two versions of the contract in MyToken1.sol
and MyToken2.sol
and diff the output of the compiler:
npm install @openzeppelin/contracts
diff --unified=0 --color \
<(solc MyToken1.sol --bin --include-path node_modules --base-path . --debug-info none --metadata-hash none) \
<(solc MyToken2.sol --bin --include-path node_modules --base-path . --debug-info none --metadata-hash none)
You'll see no differences.
The contract wizard often creates the statement "// The following functions are overrides required by Solidity" followed by a series of overrides that appear trivial. Why is this done? What happens without these override statements?
If you remove them you'll get errors like this one:
Error: Derived contract must override function "_afterTokenTransfer". Two or more base classes define function with same name and parameter types.
--> MyToken.sol:8:1:
|
8 | contract MyToken is ERC20, ERC20Permit, ERC20Votes {
| ^ (Relevant source part starts here and spans across multiple lines).
Note: Definition in "ERC20":
--> @openzeppelin/contracts/token/ERC20/ERC20.sol:351:5:
|
351 | function _afterTokenTransfer(
| ^ (Relevant source part starts here and spans across multiple lines).
Note: Definition in "ERC20Votes":
--> @openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol:194:5:
|
194 | function _afterTokenTransfer(
| ^ (Relevant source part starts here and spans across multiple lines).
The error appears when you inherit from two different contracts that both provide the same virtual function. There's more than one way to resolve this conflict - should only one be called? none? both? in what order?
The usual solution is to call the next one in the linearized inheritance order and expect it to call the other one using super
. The problem is that it depends on the user using super
correctly and being aware that the conflict exists in the first place. It's very easy not to notice it and have some contract inserted into the inheritance order silently break the chain of calls. To make sure that you are aware of all the base contracts that have a conflicting function, the compiler requires you to specify names of these contracts in the override
specifier. And to be able to add an override specifier you have to override the function.
In your case though you could easily get rid of that error by switching to MyToken is ERC20Votes
. It's the fact that ERC20
and ERC20Permit
are listed on the inheritance list as if they were independent contracts that makes the compiler complain about it. Due to the virtual inheritance it should not matter though so I suspect it's just that no one thought to make it a special case in the compiler yet. You might want to submit a feature request about that. In any case, by just not listing ERC20
and ERC20Permit
you can reduce the code to this:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract MyToken is ERC20Votes {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}
}
I don't know for sure why the wizard makes MyToken
inherit from all three but my guess is that it's just to make contract generation simpler. It does not affect the bytecode after all.