ethereum / solidity

Solidity, the Smart Contract Programming Language
https://soliditylang.org
GNU General Public License v3.0
23.12k stars 5.73k forks source link

Should private functions really clash ? #11889

Open Amxx opened 3 years ago

Amxx commented 3 years ago

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?

chriseth commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

cameel commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

Amxx commented 3 years ago

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:

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 commented 3 years ago

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 commented 2 years ago

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

k06a commented 2 years ago

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

cameel commented 2 years ago

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.

k06a commented 2 years ago

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

cameel commented 2 years ago

Sure, why not.

Rushanksavant commented 2 years ago

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 commented 2 years ago

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.

github-actions[bot] commented 1 year ago

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] commented 1 year ago

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.

Amxx commented 1 year ago

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

Amxx commented 1 year ago

@cameel can you reopen it ?

cameel commented 1 year ago

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.

github-actions[bot] commented 1 year ago

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

Amxx commented 1 year ago

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

gks2022004 commented 12 months ago

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 commented 12 months ago

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 commented 12 months ago
    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.

github-actions[bot] commented 9 months ago

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

Amxx commented 8 months ago

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

github-actions[bot] commented 5 months ago

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

k06a commented 5 months ago

Was it resolved or not?

Amxx commented 5 months ago

no

HiteshMittal07 commented 4 months ago

@Amxx I think the problem is that you are trying to inherit both the contract in AB which means bringing functions of contract A and B in context of AB which found similar name style and visibility causing name conflict and error

Amxx commented 4 months ago

which means bringing functions of contract A and B in context of AB

Functions are private within A and B. Private functions are not available in child contract, so I would argue they are not brought in the context of AB

HiteshMittal07 commented 4 months ago

@Amxx Okay yeah but i think is what is happening here , is that child contract cannot call the private function but still able to see the private functions as they are visible everywhere but can be called only inside that contract scope.

k06a commented 4 months ago

Imagine inheriting your smart contract from two different libraries, which are coincidentally using same name for different private functions. But why this parent’s private implementation should be an issue for you? Private members should have 0 exposure to the inherited contracts.

HiteshMittal07 commented 4 months ago

When AB tries to resolve the private function calls within the context of its parents, the compiler needs to distinguish between the implementations in A and B. Since the private function is not inherited, it’s not straightforward for the compiler to know which private function from the parent contracts should be considered when calls to these functions are made.

k06a commented 4 months ago

@HiteshMittal07 you should not be able to refer to private members of your parents. That’s what internal is made for. I am really struggling why this concept is so hard to understand.

github-actions[bot] commented 1 month ago

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

k06a commented 1 month ago

Still relevant