• Blog
  • Archives
  • Code
  • Talks
  • About

Nested delegate call in Solidity


(1 minute read)

Are nested delegate calls possible within Solidity? of course. I had a situation whereby I was using the Proxy upgradeability pattern but my implementation had gotten too large to deploy. So I wanted to split it up into sub-implementations, which meant needing to delegate from within a delegate.

I couldn't find any examples online so I built my own and tested it out. Here is the code below.

I'll use a modified version of Eternal Storage as the base class for my contracts so that all delegates have the exact same storage layout:

contract EternalStorage {
  mapping(string => address) dataAddress;
  mapping(string => uint256) dataUint256;

Let's look at the first contract D1:

import "./EternalStorage.sol";
contract D1 is EternalStorage {
  function setValue (uint256 i) public {
    dataUint256["value"] = i;
  function getValue () public view returns (uint256) {
    return dataUint256["value"];

The contract allows one to get and set a value. Now let's look at D2:

import "./D1.sol";
contract D2 is EternalStorage {
  using Delegate for *;
  function setValue (uint256 i) public {
    (bool success, bytes memory returnedData) = dataAddress["d1"].delegatecall(abi.encodeWithSelector(
    require(success, string(returnedData));
  function getValue () public view returns (uint256) {
    return dataUint256["value"];

In D2 we delegate the setValue() call to a D1 instance that's stored in dataAddress["d1"]. Finally, we have the D3 contract:

import "./EternalStorage.sol";
contract D3 is EternalStorage {
  constructor (address _d1, address _d2) public {
    dataAddress["d1"] = _d1;
    dataAddress["impl"] = _d2;
  function () external payable {
    address impl = dataAddress["impl"];
    assembly {
      let ptr := mload(0x40)
      calldatacopy(ptr, 0, calldatasize)
      let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0)
      let size := returndatasize
      returndatacopy(ptr, 0, size)
      switch result
      case 0 { revert(ptr, size) }
      default { return(ptr, size) }

D3 implements the proxy pattern, whereby it doesn't expose an API of its own - instead it passes on all calls to the delegate stored in dataAddress["impl"], which we will set as an instance of D2.

Thus the delegation is as follows: D3 -> D2 -> D1.

When we call D3.setValue() the EVM should end up executing D1.setValue() within D3's memory context.

Here is how the test code looks (using Truffle):

contract('Delegate test', accounts => {
  let d1
  let d2
  let d3
  beforeEach(async () => {
    d1 = await D1.new()
    d2 = await D2.new()
    const d3Proxy = await D3.new(d1.address, d2.address)
    d3 = await D2.at(d3Proxy.address)
  it('sets the value', async () => {
    await d1.setValue(3)
    await d1.getValue().should.eventually.eq(3)
    await d2.setValue(6)
    await d1.getValue().should.eventually.eq(3)
    await d2.getValue().should.eventually.eq(0)
    await d3.setValue(9)
    await d1.getValue().should.eventually.eq(3)
    await d2.getValue().should.eventually.eq(0)
    await d3.getValue().should.eventually.eq(9)

The d3.setValue() call is the key one. It shows that d3's value that gets set, not d1's or d2's.

Note: d2.setvalue() has no effect since in d2's memory space dataAddress["d1"] is empty, which means that the delegate call fails.

  • Feed
  • Email
  • Twitter
  • Github
  • Linked-in
  • Stack Overflow
© HiddenTao Ltd. Source on Github.