5

I have a library

utils.sol

library Utils {
   struct UtilType {
      unit var1;
      bool var2;
   }

  function addExtra(UtilType storage state, uint extra) public {
    state.var1 += extra;
  }
}

notice the public visibility on addExtra function in utils.sol library. and that function is being used as follows:

mycontract.sol

import "utils.sol"
contract MyContract {
   using Utils for UtilsType;
   UtilsType state;

  function foo(uint extra) public {
    state.addExtra(extra);
  }
}
  1. will changing public to external in myutils.sol reduce external function call overhead and save any gas?
  2. if so, does the gas savings only occur due to uint variable and not struct type since it's a storage memory?

2 Answers 2

20
+100

Declaring a function as external rather than public does not affect gas usage at all, neither in contracts nor in libraries.

In earlier versions of Solidity, only external functions could take calldata arguments. Making a function public would force you to use the more expensive memory arguments. This restriction was lifted in Solidity 0.6.9. This is probably where the misconception originally comes from. Using external functions used to make calls cheaper but this was not due to their visibility.

Other than that, the choice between them only affects analysis (some things may be forbidden with one or the other), not code generation. You will get the exact same bytecode in both cases.

Example

You can check for yourself that the bytecode is identical.

Let's put this simple library in a file called lib.sol:

library L {
    enum E {A, B, C}
    struct S { address[50] addresses; }

    function f(
        uint a,
        string memory b,
        bytes1[] calldata c,
        mapping(E => S[]) storage d,
        function() external e
    )
    public
    returns (
        uint,
        string memory,
        bytes1[] calldata,
        mapping(E => S[]) storage,
        function() external
    )
    {
        return (a, b, c, d, e);
    }
}

and then run the compiler twice, changing the visibility to external before the second run:

solc lib.sol --bin --metadata-hash none > public.bin
sed -i s/public/external/ lib.sol
solc lib.sol --bin --metadata-hash none > external.bin
diff public.bin external.bin

Even without the optimizer enabled the output is empty, meaning, there are absolutely no differences.

Implementation in the compiler

You can check compiler code to see that it's not just this simple example that behaves this way.

In case of external/public functions, there are two places where you might expect differences:

Note how in both cases there are no conditions distinguishing between public vs external.

The dispatch goes over a list of interface functions which includes both public and external functions. It's constructed by ContractDefinition::interfaceFunctionList(), which goes over all functions in a contract and uses FunctionDefinition::isPartOfExternalInterface() as a filter. This in turn calls Declaration::isPublic(), which, despite the name, has a >= condition on visibility so it matches external functions as well:

bool isPublic() const { return visibility() >= Visibility::Public; }

You could dive deeper into how the body is generated but you won't find any special cases there either.

2

First of all let's look what "public" means. Public, functions can call internally or externally. Internal calls is a basic jumps on EVM. So they takes more less gas than external calls. If you call public functions in your contract that is an internal call. In your situation you are calling your function by internally. If you change it to externally, you can't call it from your contract. So your answer is: There will be NO reduction. External calls doesn't copies your args to memory, they access to with calldata calls. If you are taking large size arrays, you can save gas with using external.

2
  • this is not just about public vs external. Notice that I'm discussing the visibility public vs external in a library function. Library functions are treated as base contracts during function calls and use delegate calls to invoke functions, in this case, arguments are not copied to memory, only the reference will be sent to the library contract. so my question is if it's only the reference that's being sent to the library contract, will it be any different to use external over the public in a library function to save gas? Commented Aug 27, 2021 at 14:26
  • 2
    I tried public and external for addExtra function and results are: For public: 47532 gas and 47532. So they are equal. There is no gas saving with this way. Did it answer your question? If not let me know. Commented Aug 27, 2021 at 14:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.