Solidity: Should we clarify when library code is linked vs embedded?

Created on 24 Apr 2018  路  2Comments  路  Source: ethereum/solidity

To my surprise, I get nice, fully-linked, deployable code if I compile this.

pragma solidity ^0.4.22;

import "https://github.com/OpenZeppelin/zeppelin-solidity/blob/c5d66183abcb63a90a2528b8333b2b17067629fc/contracts/token/ERC20/StandardToken.sol";

// modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/examples/SimpleToken.sol
contract SimpleToken is StandardToken {
  string public constant name = "SimpleToken"; 
  string public constant symbol = "SIMPLETOKEN"; 
  uint8 public constant decimals = 18; 

  uint256 public constant INITIAL_SUPPLY = 10000 * (10 ** uint256(decimals));

  constructor public {
    totalSupply_ = INITIAL_SUPPLY;
    balances[msg.sender] = INITIAL_SUPPLY;
    Transfer(0x0, msg.sender, INITIAL_SUPPLY);
  }
}

That surprised me (pleasantly), because the imported parent contract (indirectly) depends on Zeppelin's SafeMath library. I expected the code to include __SafeMath__..._ placeholders that would have to be linked.

I'm happy, it works, yay! But in the documentation, I can't find an explanation of when the compiler would expect to link to a library, and when it would just embed library functionality into the generated code. Pretty much all of the discussion of libraries presumes they would be separately deployed and linked.

I'd be glad to try to update the docs, if I understood the rules for when library functionality is inlined-vs-linked myself. But I don't. Can anyone help?


p.s. I am using my own tooling, rather than solc to resolve imports (maybe that's why this works? because solc sees everything in a single compilation unit?) I've placed the fully resolved generated solidity here, fwiw.

Most helpful comment

@GNSPS Thanks!

That seems like a good thing to document very clearly. (It seems like a very useful trick, to get the code reuse without the complexity of an extra module and a linkage and post-hack nervousness over the security of DELEGATECALL-based constructs.)

I see in the docs an example of a library built with internal functions, and an explanation that libraries are in the "context" of the using contract, and so can access them. From that, along with the explanation that internal functions are implemented as jumps within the code, a smarter reader than me might have been able to deduce that they would be inlined, but this seems like a feature that could be highlighted a bit more.

Now that I know what I am looking for, I see that this behavior is more explicitly documented here

Furthermore, internal functions of libraries are visible in all contracts, just as if the library were a base contract. Of course, calls to internal functions use the internal calling convention, which means that all internal types can be passed and memory types will be passed by reference and not copied. To realize this in the EVM, code of internal library functions and all functions called from therein will at compile time be pulled into the calling contract, and a regular JUMP call will be used instead of a DELEGATECALL.

Still, having read that page before and not understood the implication (that no separate library would need to be deployed if only internal functions are used), it might be worth highlighting a bit. I'll try to give that a shot.

Anyway, thank you again for resolving the mystery.

All 2 comments

When utilizing internal functions from libraries these are inlined (since it would really just be a waste of gas to not do so in most cases).

@GNSPS Thanks!

That seems like a good thing to document very clearly. (It seems like a very useful trick, to get the code reuse without the complexity of an extra module and a linkage and post-hack nervousness over the security of DELEGATECALL-based constructs.)

I see in the docs an example of a library built with internal functions, and an explanation that libraries are in the "context" of the using contract, and so can access them. From that, along with the explanation that internal functions are implemented as jumps within the code, a smarter reader than me might have been able to deduce that they would be inlined, but this seems like a feature that could be highlighted a bit more.

Now that I know what I am looking for, I see that this behavior is more explicitly documented here

Furthermore, internal functions of libraries are visible in all contracts, just as if the library were a base contract. Of course, calls to internal functions use the internal calling convention, which means that all internal types can be passed and memory types will be passed by reference and not copied. To realize this in the EVM, code of internal library functions and all functions called from therein will at compile time be pulled into the calling contract, and a regular JUMP call will be used instead of a DELEGATECALL.

Still, having read that page before and not understood the implication (that no separate library would need to be deployed if only internal functions are used), it might be worth highlighting a bit. I'll try to give that a shot.

Anyway, thank you again for resolving the mystery.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AnthonyAkentiev picture AnthonyAkentiev  路  3Comments

bshastry picture bshastry  路  3Comments

chriseth picture chriseth  路  3Comments

VoR0220 picture VoR0220  路  4Comments

axic picture axic  路  3Comments