diff --git a/Changelog.md b/Changelog.md index dbdd34448..f2fc82164 100644 --- a/Changelog.md +++ b/Changelog.md @@ -21,6 +21,7 @@ Breaking changes: Language Features: * Yul: Disallow EVM instruction `pc()`. * Yul: Disallow consecutive and trailing dots in identifiers. Leading dots were already disallowed. + * Inheritance: Allow overrides to have stricter state mutability: ``view`` can override ``nonpayable`` and ``pure`` can override ``view``. Compiler Features: diff --git a/docs/contracts/inheritance.rst b/docs/contracts/inheritance.rst index e03ac397c..343604a34 100644 --- a/docs/contracts/inheritance.rst +++ b/docs/contracts/inheritance.rst @@ -203,23 +203,29 @@ Function Overriding Base functions can be overridden by inheriting contracts to change their behavior if they are marked as ``virtual``. The overriding function must then -use the ``override`` keyword in the function header as shown in this example: +use the ``override`` keyword in the function header. +The overriding function may only change the visibility of the overridden function from ``external`` to ``public``. +The mutability may be changed to a more strict one following the order: +``nonpayable`` can be overridden by ``view`` and ``pure``. ``view`` can be overridden by ``pure``. +``payable`` is an exception and cannot be changed to any other mutability. + +The following example demonstrates changing mutability and visibility: :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.6.0 <0.8.0; + pragma solidity >0.6.99 <0.8.0; contract Base { - function foo() virtual public {} + function foo() virtual external view {} } contract Middle is Base {} contract Inherited is Middle { - function foo() public override {} + function foo() override public pure {} } For multiple inheritance, the most derived base contracts that define the same diff --git a/libsolidity/analysis/OverrideChecker.cpp b/libsolidity/analysis/OverrideChecker.cpp index 6a2ccc83a..c58fd2c85 100644 --- a/libsolidity/analysis/OverrideChecker.cpp +++ b/libsolidity/analysis/OverrideChecker.cpp @@ -587,7 +587,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr // This is only relevant for a function overriding a function. if (_overriding.isFunction()) { - if (_overriding.stateMutability() != _super.stateMutability()) + // Stricter mutability is always okay except when super is Payable + if (( + _overriding.stateMutability() > _super.stateMutability() || + _super.stateMutability() == StateMutability::Payable + ) && + _overriding.stateMutability() != _super.stateMutability() + ) overrideError( _overriding, _super, diff --git a/test/libsolidity/syntaxTests/inheritance/override/add_view.sol b/test/libsolidity/syntaxTests/inheritance/override/add_view.sol index 14c6297c3..508678514 100644 --- a/test/libsolidity/syntaxTests/inheritance/override/add_view.sol +++ b/test/libsolidity/syntaxTests/inheritance/override/add_view.sol @@ -1,5 +1,3 @@ contract B { function f() virtual public {} } -contract C is B { function f() public view {} } +contract C is B { function f() override public view {} } // ---- -// TypeError 9456: (64-91): Overriding function is missing "override" specifier. -// TypeError 6959: (64-91): Overriding function changes state mutability from "nonpayable" to "view". diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_less_strict_mutability.sol b/test/libsolidity/syntaxTests/inheritance/override/override_less_strict_mutability.sol new file mode 100644 index 000000000..4bf72532c --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_less_strict_mutability.sol @@ -0,0 +1,24 @@ +contract A { + function foo() external pure virtual returns (uint256) {} +} +contract B is A { + function foo() external pure override virtual returns (uint256) {} +} +contract C is A { + function foo() external view override virtual returns (uint256) {} +} +contract D is B, C { + function foo() external override(B, C) virtual returns (uint256) {} +} +contract E is C, B { + function foo() external pure override(B, C) virtual returns (uint256) {} +} +contract F is C, B { + function foo() external payable override(B, C) virtual returns (uint256) {} +} +// ---- +// TypeError 6959: (181-247): Overriding function changes state mutability from "pure" to "view". +// TypeError 6959: (272-339): Overriding function changes state mutability from "pure" to "nonpayable". +// TypeError 6959: (272-339): Overriding function changes state mutability from "view" to "nonpayable". +// TypeError 6959: (461-536): Overriding function changes state mutability from "view" to "payable". +// TypeError 6959: (461-536): Overriding function changes state mutability from "pure" to "payable". diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability.sol new file mode 100644 index 000000000..3c09e5722 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability.sol @@ -0,0 +1,16 @@ +contract A { + function foo() internal view virtual returns (uint256) {} +} +contract B is A { + function foo() internal pure override virtual returns (uint256) {} +} +contract C is A { + function foo() internal view override virtual returns (uint256) {} +} +contract D is B, C { + function foo() internal pure override(B, C) virtual returns (uint256) {} +} +contract E is C, B { + function foo() internal pure override(B, C) virtual returns (uint256) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability1.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability1.sol new file mode 100644 index 000000000..959774a7b --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability1.sol @@ -0,0 +1,8 @@ +contract A { + function foo() public payable virtual returns (uint256) {} +} +contract B is A { + function foo() public override virtual returns (uint256) {} +} +// ---- +// TypeError 6959: (94-153): Overriding function changes state mutability from "payable" to "nonpayable". diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability2.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability2.sol new file mode 100644 index 000000000..7e62c2023 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability2.sol @@ -0,0 +1,7 @@ +contract A { + function foo() internal virtual returns (uint256) {} +} +contract B is A { + function foo() internal view override virtual returns (uint256) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability3.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability3.sol new file mode 100644 index 000000000..e1117f2b2 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability3.sol @@ -0,0 +1,7 @@ +contract A { + function foo() internal view virtual returns (uint256) {} +} +contract B is A { + function foo() internal pure override virtual returns (uint256) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability4.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability4.sol new file mode 100644 index 000000000..da539d6df --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability4.sol @@ -0,0 +1,8 @@ +contract A { + function foo() public payable virtual returns (uint256) {} +} +contract B is A { + function foo() public view override virtual returns (uint256) {} +} +// ---- +// TypeError 6959: (94-158): Overriding function changes state mutability from "payable" to "view". diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability5.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability5.sol new file mode 100644 index 000000000..2c11faed9 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability5.sol @@ -0,0 +1,8 @@ +contract A { + function foo() public payable virtual returns (uint256) {} +} +contract B is A { + function foo() public pure override virtual returns (uint256) {} +} +// ---- +// TypeError 6959: (94-158): Overriding function changes state mutability from "payable" to "pure". diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability6.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability6.sol new file mode 100644 index 000000000..8947c357a --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability6.sol @@ -0,0 +1,8 @@ +contract A { + function foo() public virtual returns (uint256) {} +} +contract B is A { + function foo() public payable override virtual returns (uint256) {} +} +// ---- +// TypeError 6959: (86-153): Overriding function changes state mutability from "nonpayable" to "payable". diff --git a/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability7.sol b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability7.sol new file mode 100644 index 000000000..c09656608 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/override/override_stricter_mutability7.sol @@ -0,0 +1,8 @@ +contract A { + function foo() public view virtual returns (uint256) {} +} +contract B is A { + function foo() public payable override virtual returns (uint256) {} +} +// ---- +// TypeError 6959: (91-158): Overriding function changes state mutability from "view" to "payable".