solidity icon indicating copy to clipboard operation
solidity copied to clipboard

Should private functions really clash ?

Open Amxx opened this issue 4 years ago • 37 comments

Let consider the following code:

contract A {
    function __myPrivateFunction() private {
        // Does something usefull
    }
    
    function callA() public {
        __myPrivateFunction();
    }
}

contract B {
    function __myPrivateFunction() private {
        // Does something usefull
    }
    
    function callB() public {
        __myPrivateFunction();
    }
}

contract AB is A, B {}

What I am expecting

implementation of __myPrivateFunction are private, and they only make sense in the context of the contract that they are part of (A and B). They are not accessible from AB, so it is not like if a call from a function within AB wouldn't be resolvable.

IMO, this should compile, possibly with a warning, but not with an error.

What happens

I get an error:

TypeError: Derived contract must override function "__myPrivateFunction". Two or more base classes define function with same name and parameter types.

Question/Request

Is that behaviour wanted? needed? Whould it be possible to accept this kind of ghost-conflicts?

Amxx avatar Sep 03 '21 09:09 Amxx

The error message is of course bad because you cannot override the functions.

I'm fine with allowing it, but then without a warning.

chriseth avatar Sep 06 '21 13:09 chriseth

I also think that such functions should be allowed and without a warning. If private functions cannot even be referenced from inherited contracts, they should not clash.

cameel avatar Sep 06 '21 13:09 cameel

We should be careful if non-private can be changed to private in inheritance.

chriseth avatar Sep 06 '21 14:09 chriseth

Looks like it can't:

contract C {
    function f() internal virtual {}
}

contract D is C {
    function f() private override {}
}
Error: Overriding function visibility differs.
 --> test.sol:6:5:
  |
6 |     function f() private override {}
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: Overridden function is here:
 --> test.sol:2:5:
  |
2 |     function f() internal virtual {}
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

cameel avatar Sep 06 '21 14:09 cameel

Also:

contract C {
    function x() external virtual returns (uint) {}
    function y() public virtual returns (uint) {}
    function z() internal virtual returns (uint) {}
}

contract D is C {
    uint private override x;
    uint private override y;
    uint private override z;
}
Error: Identifier already declared.
 --> test.sol:9:5:
  |
9 |     uint private override y;
  |     ^^^^^^^^^^^^^^^^^^^^^^^
Note: The previous declaration is here:
 --> test.sol:3:5:
  |
3 |     function y() public virtual returns (uint) {}
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Error: Identifier already declared.
  --> test.sol:10:5:
   |
10 |     uint private override z;
   |     ^^^^^^^^^^^^^^^^^^^^^^^
Note: The previous declaration is here:
 --> test.sol:4:5:
  |
4 |     function z() internal virtual returns (uint) {}
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Error: Override can only be used with public state variables.
 --> test.sol:8:5:
  |
8 |     uint private override x;
  |     ^^^^^^^^^^^^^^^^^^^^^^^

Error: Override can only be used with public state variables.
 --> test.sol:9:5:
  |
9 |     uint private override y;
  |     ^^^^^^^^^^^^^^^^^^^^^^^

Error: Override can only be used with public state variables.
  --> test.sol:10:5:
   |
10 |     uint private override z;
   |     ^^^^^^^^^^^^^^^^^^^^^^^

cameel avatar Sep 06 '21 14:09 cameel

Shadowing makes no sense considering inaccessible functions (i.e. private), so in essence allowing this makes sense.

However it definitely opens up the possibility for crafting/hiding confusing code, given contracts can have many internal and private functions, combining that with inheritance sometimes it is really hard to decipher what is the actual code used for the contract. The virtual/override specifiers help with that.

While I do not think it is a good idea to try "saving people from mistakes" with weird rules like this, but I would put the question on the table: should this change be made in a breaking release, just in order to trigger a more thorough review from people using the language? Users tend to review their contracts more carefully when they bump a breaking release.

axic avatar Sep 08 '21 12:09 axic

@Amxx can you provide more cases that would benefit from this feature? I actually think @axic 's point is very valid here.

chriseth avatar Sep 08 '21 12:09 chriseth

My use-case is a bit complex, and one might argue that it comes from bad design on our end.


OpenZeppelin provides a lot of functionalities in its different contracts and library. We also offer a framework for upgradeable contracts. This framework includes solidity code, and javascript tooling to help use deploy and update their contracts.

This tooling, in particular, checks dangerous behaviour, such a selfdestruct (see parity bug) and delegate calls (which can lead to self destruct). We want to protect users from using these in upgradeable contracts.

We have an Address library, which includes functions such as isContract, sendValue, functionStaticCall and functionDelegateCall. While most are safe, this last one can be dangerous. In our plugin we don't have the ability to filter that effectively, and whenever a user import the library, we do check the entire library for potential vulnerabilities.

To limit false positives, we decided to:

  • Remove the functionDelegateCall for the Address library (in the upgradeable repo)
  • In the very rare places where we internally use it (in a safe way), inline a private copy with natspec comment to locally disable our security check.

The result is that we have this private function in some of our upgradeable contracts:

  • https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/utils/MulticallUpgradeable.sol#L37-L43
  • https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol#L207-L213

Now if I want to build a UUPSUpgradeable contract (inherits from ERC1967UpgradeUpgradeable) with the multicall feature ... I get my private function defined twice.

I'm sure we can rework our transpilling workflow, and the way our checks are performed ... but you asked me for my usecase so here it is.

Amxx avatar Sep 08 '21 13:09 Amxx

I don't think the concern that @axic shared is a significant one. Private functions are the easiest to trace back to their definition because it just requires a search in the contract where it is used.

frangio avatar Sep 16 '21 15:09 frangio

Had the same issue in the mid 2020: https://github.com/ethereum/solidity/issues/9610

k06a avatar Feb 02 '22 17:02 k06a

Now private works as hypothetical final/nonoverride, but I wish private methods produce no side effects outside of the contracts/libraries.

k06a avatar Feb 03 '22 07:02 k06a

We discussed this today on the call. Overall there were some concerns about safety of allowing this but no one was strongly against so we're fine with changing the behavior. This is however very low priority right now because we have other important tasks on the roadmap. It may take some time before we get to implementing this ourselves, but we'd accept a PR if someone badly wanted to have it right now.

cameel avatar Feb 09 '22 18:02 cameel

We can use GitCoin to boost this change :) Moreover, this could attract more contributors to the repo.

k06a avatar Feb 10 '22 12:02 k06a

Sure, why not.

cameel avatar Feb 10 '22 14:02 cameel

Hello @Amxx, I thought of an approach but not sure if this solves the exact problem. Please have a look:

contract A {
    uint x; 
    function __myPrivateFunction(uint _num) private { // some state change
        x=_num;
    }
    
    function callA() public {
        __myPrivateFunction(1);
    }
}

contract B {
    uint x;
    function __myPrivateFunction(uint _num) private { // some state change
        x=_num;
    }
    
    function callB() public {
        __myPrivateFunction(2);
    }
}

Now since we are not able to inherit A and B together, we creat contracts that delegate calls to A and B:

contract A_caller{
    uint public x_A;
    A contractA;

    function set_contractA(address _A) external {
        contractA = A(_A);
    }

    function call_contractA() public {
        (bool success,) = address(contractA).delegatecall(abi.encodeWithSignature("callA()"));
        require(success);
    }
}

contract B_caller{
    uint public x_B;
    B contractB;

    function set_contractB(address _B) external {
        contractB = B(_B);
    }

    function call_contractB() public {
        (bool success,) = address(contractB).delegatecall(abi.encodeWithSignature("callB()"));
        require(success);
    }
}

And inherit these contracts together

contract AB is A_caller, B_caller{  }

This will allow us to access all the functions of A and B using the state of A_caller and B_caller.

Best regards!

Rushanksavant avatar Jun 04 '22 20:06 Rushanksavant

In that you have to worry about storage compatibility of A and B, you can't call internal functions, you possibly have to do many checks...

Basically, you are arguing that doing something like a diamond proxy is a solution to the private function conflict.

While it is technically true, I really don't think it's a reasonable/acceptable solution.

Amxx avatar Jun 04 '22 20:06 Amxx

This issue has been marked as stale due to inactivity for the last 90 days. It will be automatically closed in 7 days.

github-actions[bot] avatar Mar 21 '23 12:03 github-actions[bot]

Hi everyone! This issue has been automatically closed due to inactivity. If you think this issue is still relevant in the latest Solidity version and you have something to contribute, feel free to reopen. However, unless the issue is a concrete proposal that can be implemented, we recommend starting a language discussion on the forum instead.

github-actions[bot] avatar Mar 28 '23 12:03 github-actions[bot]

This should not have been close. The issue remains present and IMO a fix would be welcome.

Amxx avatar Mar 31 '23 12:03 Amxx

@cameel can you reopen it ?

Amxx avatar Mar 31 '23 12:03 Amxx

Sure. We're in the process of closing off stale issues that we're unlikely to ever to implement ourselves but since this one is pretty much waiting for an external contribution (and even has a bounty on it), I guess keeping it open is fine.

cameel avatar Mar 31 '23 13:03 cameel

This issue has been marked as stale due to inactivity for the last 90 days. It will be automatically closed in 7 days.

github-actions[bot] avatar Jun 30 '23 12:06 github-actions[bot]

This issue is still relevant and is addressed by a pending PR. It should bot because for stale.

Amxx avatar Jul 05 '23 20:07 Amxx

when you inherit contracts, their functions and state variables are available to the derived contract. This includes functions marked as private. While you might expect that private functions in contract A and contract B would not be accessible from contract AB, Solidity allows access to these functions when they are inherited.

This behavior is by design in Solidity. When you inherit from contracts A and B in contract AB, all the functions and state variables from A and B, including the private ones, are part of contract AB. This allows the derived contract to access and use those functions as if they were its own.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract A {
    function __myPrivateFunction() private {
        // Does something useful
    }

    function callA() public {
        __myPrivateFunction();
    }
}

contract B {
    function __myPrivateFunction() private {
        // Does something useful
    }

    function callB() public {
        __myPrivateFunction();
    }
}

contract AB is A, B {
    function callAB() public {
        __myPrivateFunction(); // This works because it's inherited
        callA(); // This works too
        callB(); // This works too
    }
}

In this code, AB inherits from both A and B, and it can access and call the private functions from both of them. This behavior follows Solidity's inheritance model, where all functions from parent contracts are included in the derived contract.

If you want to prevent access to the private functions from the derived contract, you should consider changing the access level of those functions to internal or external, depending on your intended use case and visibility requirements.

gks2022004 avatar Oct 02 '23 14:10 gks2022004

This allows the derived contract to access and use those functions as if they were its own

Private functions (and variable) are clearly not accessible from the derived contract like public or internal ones are. Sure, they make it into the final bytecode, but there is no reason to not "namespace" them.

A.__myPrivateFunction access really is limited to A. It is part of AB's bytecode, and it can be called indirectly through callA ... but as far as AB is concerned, it could have any name, that would not matter. Therefore, I strongly believe that it should be considered as something in A that should not clash with anything in B or AB.

Amxx avatar Oct 02 '23 16:10 Amxx

    function callAB() public {
        __myPrivateFunction(); // This works because it's inherited
        callA(); // This works too
        callB(); // This works too
    }

This does NOT work. Unlike internal functions, private functions are NOT accessible in child contract.

Amxx avatar Oct 06 '23 09:10 Amxx

This issue has been marked as stale due to inactivity for the last 90 days. It will be automatically closed in 7 days.

github-actions[bot] avatar Jan 04 '24 12:01 github-actions[bot]

Again, this issue is still relevant and is addressed by a pending PR. It should not be marked as stale.

Amxx avatar Jan 05 '24 14:01 Amxx

This issue has been marked as stale due to inactivity for the last 90 days. It will be automatically closed in 7 days.

github-actions[bot] avatar Apr 06 '24 12:04 github-actions[bot]

Was it resolved or not?

k06a avatar Apr 06 '24 12:04 k06a