13

According to the documenation when using a library with internal functions, the function's bytecode gets inlined into the calling contract's function.

Can someone confirm if the following is therefore a correct observation?

library A {
   function doSomething() constant returns (uint) { return 10; }
   function doInternal() internal constant returns (uint) { return 99; }
}

library B {
   function doSomething() constant returns (uint) { return 2000; }
   function doInternal() internal constant returns (uint) { return 8888; }
}

contract Consumer {
   function execute() {
      uint x = A.doSomething();
      uint y = A.doInternal();
   }
}
  1. Compile the Consumer contract to produce an unlinked .bin
  2. Deploy library B
  3. Use solc to link A references in the Consumer contract to the address of deployed library B
  4. Deploy the Consumer contract

When invoking the execute() of the Consumer contract, the result would be x == 2000 y == 99

This means that the doInternal() function will always yield 99 as a result, because it was inlined into the consumer contract and was not affected by the linking.

This means it's possible when using libraries and linking to end up with essentially calling functions across multiple libraries. A rather dangerous situation as it's a fact that is hidden from most users and not explicit in the documentation.

1
  • 2
    You should open an issue on the Solidity GitHub. This definitely needs to be addressed Commented Nov 21, 2017 at 22:23

1 Answer 1

5

You are indeed correct. Plugging this code into Remix, and looking at the disassembly gives:

.code
  PUSH 60           contract Consumer {\n   functi...
  PUSH 40           contract Consumer {\n   functi...
  MSTORE            contract Consumer {\n   functi...
  CALLVALUE             contract Consumer {\n   functi...
  ISZERO            contract Consumer {\n   functi...
  PUSH [tag] 1          contract Consumer {\n   functi...
  JUMPI             contract Consumer {\n   functi...
  PUSH 0            contract Consumer {\n   functi...
  DUP1          contract Consumer {\n   functi...
  REVERT            contract Consumer {\n   functi...
tag 1           contract Consumer {\n   functi...
  JUMPDEST          contract Consumer {\n   functi...
tag 2           contract Consumer {\n   functi...
  JUMPDEST          contract Consumer {\n   functi...
  PUSH #[$] 0000000000000000000000000000000000000000000000000000000000000000            contract Consumer {\n   functi...
  DUP1          contract Consumer {\n   functi...
  PUSH [$] 0000000000000000000000000000000000000000000000000000000000000000         contract Consumer {\n   functi...
  PUSH 0            contract Consumer {\n   functi...
  CODECOPY          contract Consumer {\n   functi...
  PUSH 0            contract Consumer {\n   functi...
  RETURN            contract Consumer {\n   functi...
.data
  0:
    .code
      PUSH 60           contract Consumer {\n   functi...
      PUSH 40           contract Consumer {\n   functi...
      MSTORE            contract Consumer {\n   functi...
      PUSH 0            contract Consumer {\n   functi...
      CALLDATALOAD          contract Consumer {\n   functi...
      PUSH 100000000000000000000000000000000000000000000000000000000            contract Consumer {\n   functi...
      SWAP1             contract Consumer {\n   functi...
      DIV           contract Consumer {\n   functi...
      PUSH FFFFFFFF         contract Consumer {\n   functi...
      AND           contract Consumer {\n   functi...
      DUP1          contract Consumer {\n   functi...
      PUSH 61461954         contract Consumer {\n   functi...
      EQ            contract Consumer {\n   functi...
      PUSH [tag] 2          contract Consumer {\n   functi...
      JUMPI             contract Consumer {\n   functi...
    tag 1           contract Consumer {\n   functi...
      JUMPDEST          contract Consumer {\n   functi...
      PUSH 0            contract Consumer {\n   functi...
      DUP1          contract Consumer {\n   functi...
      REVERT            contract Consumer {\n   functi...
    tag 2           function execute() {\n      ui...
      JUMPDEST          function execute() {\n      ui...
      CALLVALUE             function execute() {\n      ui...
      ISZERO            function execute() {\n      ui...
      PUSH [tag] 3          function execute() {\n      ui...
      JUMPI             function execute() {\n      ui...
      PUSH 0            function execute() {\n      ui...
      DUP1          function execute() {\n      ui...
      REVERT            function execute() {\n      ui...
    tag 3           function execute() {\n      ui...
      JUMPDEST          function execute() {\n      ui...
      PUSH [tag] 4          function execute() {\n      ui...
      PUSH [tag] 5          function execute() {\n      ui...
      JUMP          function execute() {\n      ui...
    tag 4           function execute() {\n      ui...
      JUMPDEST          function execute() {\n      ui...
      STOP          function execute() {\n      ui...
    tag 5           function execute() {\n      ui...
      JUMPDEST          function execute() {\n      ui...
      PUSH 0            uint x
      DUP1          uint y
      PUSHLIB browser/Untitled.sol:A            A
      PUSH 82692679         A.doSomething
      PUSH 0            A.doSomething()
      PUSH 40           A.doSomething()
      MLOAD             A.doSomething()
      PUSH 20           A.doSomething()
      ADD           A.doSomething()
      MSTORE            A.doSomething()
      PUSH 40           A.doSomething()
      MLOAD             A.doSomething()
      DUP2          A.doSomething()
      PUSH FFFFFFFF         A.doSomething()
      AND           A.doSomething()
      PUSH 100000000000000000000000000000000000000000000000000000000            A.doSomething()
      MUL           A.doSomething()
      DUP2          A.doSomething()
      MSTORE            A.doSomething()
      PUSH 4            A.doSomething()
      ADD           A.doSomething()
      PUSH 20           A.doSomething()
      PUSH 40           A.doSomething()
      MLOAD             A.doSomething()
      DUP1          A.doSomething()
      DUP4          A.doSomething()
      SUB           A.doSomething()
      DUP2          A.doSomething()
      DUP7          A.doSomething()
      DUP1          A.doSomething()
      EXTCODESIZE           A.doSomething()
      ISZERO            A.doSomething()
      ISZERO            A.doSomething()
      PUSH [tag] 7          A.doSomething()
      JUMPI             A.doSomething()
      PUSH 0            A.doSomething()
      DUP1          A.doSomething()
      REVERT            A.doSomething()
    tag 7           A.doSomething()
      JUMPDEST          A.doSomething()
      PUSH 2C6          A.doSomething()
      GAS           A.doSomething()
      SUB           A.doSomething()
      DELEGATECALL          A.doSomething()
      ISZERO            A.doSomething()
      ISZERO            A.doSomething()
      PUSH [tag] 8          A.doSomething()
      JUMPI             A.doSomething()
      PUSH 0            A.doSomething()
      DUP1          A.doSomething()
      REVERT            A.doSomething()
    tag 8           A.doSomething()
      JUMPDEST          A.doSomething()
      POP           A.doSomething()
      POP           A.doSomething()
      POP           A.doSomething()
      PUSH 40           A.doSomething()
      MLOAD             A.doSomething()
      DUP1          A.doSomething()
      MLOAD             A.doSomething()
      SWAP1             A.doSomething()
      POP           A.doSomething()
      SWAP2             uint x = A.doSomething()
      POP           uint x = A.doSomething()
      PUSH [tag] 9          A.doInternal()
      PUSH [tag] 10         A.doInternal
      JUMP [in]         A.doInternal()
    tag 9           A.doInternal()
      JUMPDEST          A.doInternal()
      SWAP1             uint y = A.doInternal()
      POP           uint y = A.doInternal()
    tag 6           function execute() {\n      ui...
      JUMPDEST          function execute() {\n      ui...
      POP           function execute() {\n      ui...
      POP           function execute() {\n      ui...
      JUMP [out]            function execute() {\n      ui...
    tag 10          function doInternal() internal...
      JUMPDEST          function doInternal() internal...
      PUSH 0            uint
      PUSH 63           99
      SWAP1             return 99
      POP           return 99
    tag 11          function doInternal() internal...
      JUMPDEST          function doInternal() internal...
      SWAP1             function doInternal() internal...
      JUMP [out]            function doInternal() internal...
    .data

The copied in code is found at tag 10 in the data section, and clearly hard-codes 99.

Note that in theory, it could be possible to copy the code from an existing contract at contract creation time. EVM bytecode does have the EXTCODECOPY instruction, that can copy code from another contract. However in practice, EVM bytecode has no concept of a function (Solidity implements internal functions purely in terms of jumps), and AFAIK, there is no simple way to determine where to copy the function definition from.

2
  • 1
    A post on fighting the contract size limit refers to this post w/r/t declaring library functions internal. It's clear that it won't help the size limit problem. I gather library functions declared public on the hand won't be inlined. But what about private functions in a library? I have some support functions in my library which aren't needed externally, but if I declare them private will it also bloat the contract which includes the library?
    – GGizmos
    Commented Apr 9, 2022 at 20:31
  • 1
    I suspect private functions would suffer the same fate as internal ones. They don't have an externally callable ABI, so there's no way to reach them via DELEGATECALL, so the compiler has no better options than inlining. But this may be a good topic for a new question.
    – James_pic
    Commented Apr 11, 2022 at 8:16

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.