9

I have a library which I want to deploy followed by a number of differing contracts which link to it. However I seem to me missing some understanding about library deployment and utilisation.

The library contains structs and functional logic and the contract I want to deploy afterwards is a test harness consisting of a state variable of type struct defined in the library and public wrappers of the library functions.

The compilation output isn't what I expected though. The library binary is very little (<80 bytes) while contract binary is very large (>5kb) and appears to contain the majority of the bytecode from the library. Furthermore, the contract bytecode has no linker place holder for the library address as I've seen in other examples

The code otherwise compiles fine and runs in Mix.

So the crucial question is, How do I compile and deploy the stand alone library?

  • Can you include a shrunk example library and contract examples which reproduce your problem? – Xavier Leprêtre B9lab Aug 26 '16 at 13:31
  • figured it out. See answer below for code example and explaination... – o0ragman0o Aug 27 '16 at 4:55
5

Ok I've figured out what is happening.

In a library the use of the internal accessor to a function will inline that function into the calling contract's bytecode.

In my contract, all my library functions were modified as internal so there was actually nothing left to compile and no ABI to export.

This can be illustrated in Remix with the following example:

library l_Intl {
    struct lstore { uint num; }
    function set (lstore storage self, uint _num) internal {
        self.num = _num;
    }
}

contract c_Intl {
    l_Intl.lstore cstore;
    function set (uint _num) { l_Intl.set(cstore, _num); }
}

library l_Pub {
    struct lstore { uint num; }
    function set (lstore storage self, uint _num) {
        self.num = _num;
    }
}

contract c_Pub {
    l_Pub.lstore cstore;
    function set (uint _num) { l_Pub.set(cstore, _num); }
}

Here we have two near identical libraries and contracts. The only difference is that one library uses the internal modifier on the set function while the other defaults to public. The contracts are identical except for the library chosen.

Studying the assembly, it can be seen from the bytecode that the contract using the internal library in-lines the library bytecode into the contract bytecode while the contract using the public library uses DELEGATECALL

The docs have this to say which I now understand to a much fuller extent:

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. In order to realise this in the EVM, code of internal library functions (and all functions called from therein) will be pulled into the calling contract and a regular JUMP call will be used instead of a DELEGATECALL.

My original assumption was that internal library bytecode would be pulled into the calling contract at runtime and not at compile time.

  • So internal functions are inlined but doesn't that defeat the purpose of the optimization libraries are meant to provide? I assume that the deployment cost will now be just as high as if we didn't use a library. -- This makes me think it is better to mark lib functions as external and then use delegate call instead of calling them directly. Thoughts? – valem Nov 23 '18 at 4:34
  • 2
    For libraries with small functions, the compiled bytecode is often less than the link bytecode (e.g. delegatecall address of external library and call wrappers) inserted in each place an external function is called. So in that sense internal libraries can be considerably more optimised. – o0ragman0o Nov 24 '18 at 11:34
0

The previous answer was wrong; every library has to be deployed separately to the network prior to invoking its methods. The following example runs in browser solidity:

library L {

    function add(uint256 a, uint256 b) returns (uint256) {
        return a + b;    
    }

}

contract C {

    function thatAdd(uint256 a, uint256 b) returns (uint256) {
        return L.add(a, b);
    }

    function thisAdd(uint256 a, uint256 b) returns (uint256) {
        return a + b;
    }

}

but the contract fails to deploy (tested with geth v1.4.10):

Error: The contract code couldn't be stored, please check your gas amount. undefined

The error is not related to the gas amount since it was tested from an account with 0xFFFFFFFFFFF worth of balance (in Ether).

When disassembled the code for the library is:

[1] PUSH1 0x60 
[2] BLOCKHASH 
[3] MSTORE 
[5] PUSH1 0x85 
[6] DUP1 
[8] PUSH1 0x10 
[10] PUSH1 0x00 
[11] CODECOPY 
[13] PUSH1 0x00 
[14] RETURN 
[21] PUSH6 0x0306e20afc71 
[22] POP 
[24] PUSH1 0x60 
[26] PUSH1 0x40 
[27] MSTORE 
[29] PUSH1 0x00 
[30] CALLDATALOAD 
[60] PUSH29 0x0100000000000000000000000000000000000000000000000000000000 
[61] SWAP1 
[62] DIV 
[63] DUP1 
[68] PUSH4 0x771602f7 
[69] EQ 
[71] PUSH1 0x41 
[72] JUMPI 
[74] PUSH1 0x3d 
[75] JUMP 
[76] JUMPDEST 
[78] PUSH1 0x07 
[79] JUMP 
[80] JUMPDEST 
[82] PUSH1 0x5e 
[84] PUSH1 0x04 
[85] DUP1 
[86] DUP1 
[87] CALLDATALOAD 
[88] SWAP1 
[90] PUSH1 0x20 
[91] ADD 
[92] SWAP1 
[93] SWAP2 
[94] SWAP1 
[95] DUP1 
[96] CALLDATALOAD 
[97] SWAP1 
[99] PUSH1 0x20 
[100] ADD 
[101] SWAP1 
[102] SWAP2 
[103] SWAP1 
[104] POP 
[105] POP 
[107] PUSH1 0x74 
[108] JUMP 
[109] JUMPDEST 
[111] PUSH1 0x40 
[112] MLOAD 
[113] DUP1 
[114] DUP3 
[115] DUP2 
[116] MSTORE 
[118] PUSH1 0x20 
[119] ADD 
[120] SWAP2 
[121] POP 
[122] POP 
[124] PUSH1 0x40 
[125] MLOAD 
[126] DUP1 
[127] SWAP2 
[128] SUB 
[129] SWAP1 
[130] RETURN 
[131] JUMPDEST 
[133] PUSH1 0x00 
[134] DUP2 
[135] DUP4 
[136] ADD 
[137] SWAP1 
[138] POP 
[140] PUSH1 0x7f 
[141] JUMP 
[142] JUMPDEST 
[143] SWAP3 
[144] SWAP2 
[145] POP 
[146] POP 
[147] JUMP 

and the code for the contract using it:

[1] PUSH1 0x60 
[2] BLOCKHASH 
[3] MSTORE 
[5] PUSH1 0xf3 
[6] DUP1 
[9] PUSH2 0x0011 
[11] PUSH1 0x00 
[12] CODECOPY 
[14] PUSH1 0x00 
[15] RETURN 
[17] PUSH1 0x60 
[19] PUSH1 0x40 
[20] MSTORE 
[22] PUSH1 0x00 
[23] CALLDATALOAD 
[53] PUSH29 0x0100000000000000000000000000000000000000000000000000000000 
[54] SWAP1 
[55] DIV 
[56] DUP1 
[61] PUSH4 0xaa4a75d3 
[62] EQ 
[64] PUSH1 0x37 
[65] JUMPI 
[67] PUSH1 0x35 
[68] JUMP 
[69] JUMPDEST 
[70] STOP 
[71] JUMPDEST 
[73] PUSH1 0x54 
[75] PUSH1 0x04 
[76] DUP1 
[77] DUP1 
[78] CALLDATALOAD 
[79] SWAP1 
[81] PUSH1 0x20 
[82] ADD 
[83] SWAP1 
[84] SWAP2 
[85] SWAP1 
[86] DUP1 
[87] CALLDATALOAD 
[88] SWAP1 
[90] PUSH1 0x20 
[91] ADD 
[92] SWAP1 
[93] SWAP2 
[94] SWAP1 
[95] POP 
[96] POP 
[98] PUSH1 0x6a 
[99] JUMP 
[100] JUMPDEST 
[102] PUSH1 0x40 
[103] MLOAD 
[104] DUP1 
[105] DUP3 
[106] DUP2 
[107] MSTORE 
[109] PUSH1 0x20 
[110] ADD 
[111] SWAP2 
[112] POP 
[113] POP 
[115] PUSH1 0x40 
[116] MLOAD 
[117] DUP1 
[118] SWAP2 
[119] SUB 
[120] SWAP1 
[121] RETURN 
[122] JUMPDEST 
[124] PUSH1 0x00 
[145] PUSH20 0x__L_____________________________________ 
[150] PUSH4 0x771602f7 
[151] DUP5 
[152] DUP5 
[154] PUSH1 0x40 
[155] MLOAD 
[156] DUP4 
[186] PUSH29 0x0100000000000000000000000000000000000000000000000000000000 
[187] MUL 
[188] DUP2 
[189] MSTORE 
[191] PUSH1 0x04 
[192] ADD 
[193] DUP1 
[194] DUP4 
[195] DUP2 
[196] MSTORE 
[198] PUSH1 0x20 
[199] ADD 
[200] DUP3 
[201] DUP2 
[202] MSTORE 
[204] PUSH1 0x20 
[205] ADD 
[206] SWAP3 
[207] POP 
[208] POP 
[209] POP 
[211] PUSH1 0x20 
[213] PUSH1 0x40 
[214] MLOAD 
[215] DUP1 
[216] DUP4 
[217] SUB 
[218] DUP2 
[219] DUP7 
[220] DUP1 
[221] EXTCODESIZE 
[222] ISZERO 
[224] PUSH1 0x02 
[225] JUMPI 
[227] PUSH1 0x32 
[228] GAS 
[229] SUB 
[230] DELEGATE_CALL 
[231] ISZERO 
[233] PUSH1 0x02 
[234] JUMPI 
[235] POP 
[236] POP 
[237] POP 
[239] PUSH1 0x40 
[240] MLOAD 
[241] DUP1 
[242] MLOAD 
[243] SWAP1 
[245] PUSH1 0x20 
[246] ADD 
[247] POP 
[248] SWAP1 
[249] POP 
[251] PUSH1 0xed 
[252] JUMP 
[253] JUMPDEST 
[254] SWAP3 
[255] SWAP2 
[256] POP 
[257] POP 
[258] JUMP 

The loading point for the library is at:

[145] PUSH20 0x__L_____________________________________ 
[150] PUSH4 0x771602f7 
[151] DUP5 
[152] DUP5 
[154] PUSH1 0x40 
[155] MLOAD 

The actual call to the library function is done by:

[221] EXTCODESIZE 
[222] ISZERO 
[224] PUSH1 0x02 
[225] JUMPI 
[227] PUSH1 0x32 
[228] GAS 
[229] SUB 
[230] DELEGATE_CALL 
[231] ISZERO 
[233] PUSH1 0x02 

Most of the code handles, gas cost and function argument forwarding and retrieving the result from the call.

  • 1
    This is contrary to the documentation you linked to. – o0ragman0o Aug 24 '16 at 23:19
  • "Libraries are similar to contracts, but their purpose is that they are deployed only once at a specific address and their code is reused using the DELEGATECALL (CALLCODE until Homestead) feature of the EVM." This specifically states that a library is deployed once and so not compiled into the bytecode of a utilising contract. However my own experience appears to be contrary which is where my confusion arrises. – o0ragman0o Aug 26 '16 at 0:47
  • I've updated my answer. The reason why contracts making use of libraries are larger in size than expected is because of function argument and gas cost management. – Sebi Aug 26 '16 at 9:28
  • Your original statements regarding compile time code inclusion turned out to be somewhat correct but only for library functions using the internal accessor. See my answer above for example. – o0ragman0o Aug 27 '16 at 5:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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