Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should private functions really clash ? #11889

Open
Amxx opened this issue Sep 3, 2021 · 31 comments · May be fixed by #14357
Open

Should private functions really clash ? #11889

Amxx opened this issue Sep 3, 2021 · 31 comments · May be fixed by #14357
Labels
bounty worthy 💰 help wanted low effort There is not much implementation work to be done. The task is very easy or tiny. low impact Changes are not very noticeable or potential benefits are limited. nice to have We don’t see a good reason not to have it but won’t go out of our way to implement it.
Projects

Comments

@Amxx
Copy link

Amxx commented Sep 3, 2021

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?

@cameel cameel added the language design :rage4: Any changes to the language, e.g. new features label Sep 3, 2021
@cameel cameel added this to New issues in Solidity via automation Sep 3, 2021
@cameel cameel moved this from New issues to Design Backlog in Solidity Sep 6, 2021
@chriseth
Copy link
Contributor

chriseth commented Sep 6, 2021

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

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

@cameel
Copy link
Member

cameel commented Sep 6, 2021

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.

@chriseth
Copy link
Contributor

chriseth commented Sep 6, 2021

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

@cameel
Copy link
Member

cameel commented Sep 6, 2021

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
Copy link
Member

cameel commented Sep 6, 2021

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;
   |     ^^^^^^^^^^^^^^^^^^^^^^^

@axic
Copy link
Member

axic commented Sep 8, 2021

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.

@chriseth
Copy link
Contributor

chriseth commented Sep 8, 2021

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

@Amxx
Copy link
Author

Amxx commented Sep 8, 2021

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:

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.

@frangio
Copy link
Contributor

frangio commented Sep 16, 2021

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.

@k06a
Copy link

k06a commented Feb 2, 2022

Had the same issue in the mid 2020: #9610

@k06a
Copy link

k06a commented Feb 3, 2022

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

@cameel
Copy link
Member

cameel commented Feb 9, 2022

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 cameel moved this from Design Backlog to Implementation Backlog in Solidity Feb 9, 2022
@k06a
Copy link

k06a commented Feb 10, 2022

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

@cameel
Copy link
Member

cameel commented Feb 10, 2022

Sure, why not.

@Rushanksavant
Copy link

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!

@Amxx
Copy link
Author

Amxx commented Jun 4, 2022

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.

@cameel cameel added medium effort Default level of effort medium impact Default level of impact nice to have We don’t see a good reason not to have it but won’t go out of our way to implement it. labels Sep 26, 2022
Solidity automation moved this from Implementation Backlog to Done Mar 28, 2023
@Amxx
Copy link
Author

Amxx commented Mar 31, 2023

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

@Amxx
Copy link
Author

Amxx commented Mar 31, 2023

@cameel can you reopen it ?

@cameel
Copy link
Member

cameel commented Mar 31, 2023

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 cameel reopened this Mar 31, 2023
Solidity automation moved this from Done to In progress Mar 31, 2023
@cameel cameel moved this from In progress to Icebox in Solidity Mar 31, 2023
@cameel cameel removed closed due inactivity The issue/PR was automatically closed due to inactivity. stale The issue/PR was marked as stale because it has been open for too long. language design :rage4: Any changes to the language, e.g. new features labels Mar 31, 2023
davidBar-On added a commit to davidBar-On/solidity that referenced this issue Jun 25, 2023
davidBar-On added a commit to davidBar-On/solidity that referenced this issue Jun 27, 2023
@github-actions
Copy link

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 github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Jun 30, 2023
@Amxx
Copy link
Author

Amxx commented Jul 5, 2023

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

@github-actions github-actions bot removed the stale The issue/PR was marked as stale because it has been open for too long. label Jul 6, 2023
@gks2022004
Copy link

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.

@Amxx
Copy link
Author

Amxx commented Oct 2, 2023

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
Copy link
Author

Amxx commented Oct 6, 2023

    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.

Copy link

github-actions bot commented Jan 4, 2024

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 github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Jan 4, 2024
@Amxx
Copy link
Author

Amxx commented Jan 5, 2024

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

@github-actions github-actions bot removed the stale The issue/PR was marked as stale because it has been open for too long. label Jan 6, 2024
davidBar-On added a commit to davidBar-On/solidity that referenced this issue Jan 27, 2024
Copy link

github-actions bot commented Apr 6, 2024

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 github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Apr 6, 2024
@k06a
Copy link

k06a commented Apr 6, 2024

Was it resolved or not?

@Amxx
Copy link
Author

Amxx commented Apr 6, 2024

no

@github-actions github-actions bot removed the stale The issue/PR was marked as stale because it has been open for too long. label Apr 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bounty worthy 💰 help wanted low effort There is not much implementation work to be done. The task is very easy or tiny. low impact Changes are not very noticeable or potential benefits are limited. nice to have We don’t see a good reason not to have it but won’t go out of our way to implement it.
Projects
No open projects
Solidity
  
Icebox
Development

Successfully merging a pull request may close this issue.

8 participants