Blocksism Labs Logo

3 minutes read

Solidity: Delegatecall vs Call vs Library

Difference between delegatecall, call functions as well as use of libraries.

Michał Mirończuk
30/10/2020 12:00 AM

Introduction 💬

One of the most important aspects in solidity programming are low level delegatecall, call functions as well as use of libraries. In this series of posts I gonna try to explain you how they work, in what context they work in as well as show you potential vulnerabilities ☠️ and unsafe ⚡ use.

First of all I want to emphasize that in this post I have used solidity version 0.7.0. I think that is a thing worth mentioning because of the dynamic development of the solidity language 😉.

Demonstration Code ✏️

By these 30 lines of code below we are able to fish out and test necessary behaviours of the functions as well as the context which they are located. 😎😎😎

Take a look...

      pragma solidity ^0.7.0;

      contract ImplementationContract {
          event testEvent(address txOrigin, address msgSenderAddress, address _from, uint msgValue);
          function doSomething() public payable {
              emit testEvent(tx.origin, msg.sender, address(this), msg.value);
          }
      }

      library ImplementationLib {
          event testEvent(address txOrigin, address msgSenderAddress, address _from, uint msgValue);
          function doSomething() public {
              emit testEvent(tx.origin, msg.sender, address(this), msg.value);
          }
      }
      contract CallingContract {
        address implementationContractAddress = address(new ImplementationContract());

        function callImplementationContract() payable public {
            implementationContractAddress.call{value: 0.5 ether}(abi.encodeWithSignature("doSomething()"));
        }

        function delegateCallToImplementationContract() payable public {
            implementationContractAddress.delegatecall(abi.encodeWithSignature("doSomething()"));
        }

          function callImplementationLib() payable public {
            ImplementationLib.doSomething();
        }
      }

In the code snippet we can distinguish exactly 3 entities. Going from the top there are ImplementationContract contract and ImplementationLib library where logic is implemented. Below them CallingContract as a third entity which we will use as a proxy to invoke logic using ours today's heroic functions: delegatecall, call and library call. (ImplementationLib) 🦸🦸‍♂️🦸🏻‍♀️

Graphical presentation:

Diagram

Transaction properties (tx.origin, msg.sender, msg.value) ✏️

Notice that ImplementationContract as well as ImplementationLib emit testEvents embodied as:

       event testEvent(address txOrigin, address msgSenderAddress, address _from, uint msgValue);

They are emitted in doSomething() methods. Thanks to them we can easily elicit calling context with transaction properties depending on the function (delegatecall or call) used.

Let's run the functions and see the events' outputs for each call! 🔥

Working in Remix IDE, after contract deployment we have got following addresses:

  • User wallet address: 0x5B3...dC4
  • CallingContract address: 0xD7A...71B
  • ImplementationContract address: 0xd84...397
  • ImplementationLib of course has no address

Transactions have been invoked one by one with 1 ether in value.

Function Call tx.origin msg.sender from msg.value
delegatecall 0x5B3...dC4 (User) 0x5B3...dC4 (User) 0xD7A...71B (CallingContract) 1
call 0x5B3...dC4 (User) 0xD7A...71B (CallingContract) 0xd84...397 (ImplementationContract) 0
library call 0x5B3...dC4 (User) 0x5B3...dC4 (User) 0xD7A...71B (CallingContract) 1

   

Conclusion ✏️

  • Call

Now we are able to say that using call function to the ImplementationContract context changes, it calls an instance (instance here is an important word) of the ImplementationContract. Transaction properties are different in comparison to delegated call and library call.

  • Delegatecall & library call

Delegatecall as well as library call do not change a context. It means that caller imports, pulls code into itself and uses calling function in its own context.

Good to know 💡💡💡

Futhermore, the example shows us tx.origin property behaviour. It gives the origin of the transaction. A tx.origin property address will be always a user wallet address.

Stackoverflow: "In a simple call chain A->B->C->D, inside D msg.sender will be C, and tx.origin will be A."

What's next? 🤔

Remember that using low level calls function can lead to devastating results. That's why we need to use them consciously. So... let's go to the next post and see potential risks ⚡ and vulnerabilities ☠️.

Moreover, knowledge of calls and delegatecalls is the key to understanding Upgradable Smart Contracts, which we discussed [here](/solidity-upgradable-token-contract

Tags:
Development

Michał Mirończuk

Blockchain Developer

Experienced Blockchain/Full Stack Developer with a 6-year tenure in the crypto realm.

Member since Mar 15, 2019

Latest Posts

03 Jan 20213 minutes read
Solidity: Upgradable Contracts, Tokens
Michał Mirończuk
Michał Mirończuk
Michał Mirończuk
View All

Get Free
Quote

hire@blocksism.com

Sign Up For Newsletter

Receive 50% discount on first project