Mutable Call In Static Context error when using delegatecall

When I do something like this it gives me a Mutable Call In Static Context error. Can I not use delegate call like this? If that’s the case how does upgradeability work?

contract MainStateContract {

  address thingToTransfer; // some ERC20 address
  address delegateCallContract;
  
  delegateTransfer () public {
 
    (bool success, bytes memory result) =delegateCallContract.delegatecall(...args);

  }
}

contract DelegateCallContract {

  transferSomething (address addr) public {
    addr.transfer(....);
  }

}
1 Like

Hi @realisation,

I assume that you can use delegatecall as per your example (I hadn't used delegatecall prior to this so am newish to the concept).

The Solidity documentation has the following information:

https://solidity.readthedocs.io/en/v0.5.12/introduction-to-smart-contracts.html#delegatecall-callcode-and-libraries
There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.

I created a working example below of a MainStateContract using delegatecall to call a DelegateCallContract which then transfers an ERC20 token from MainStateContract to a recipient. I used OpenZeppelin Test Environment and mocha to run the test.

I didn't get the Mutable Call In Static Context error that you received.
If you can share minimal code which generates the error, that would be great, and then I can try to reproduce.

DelegateCallContract.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract DelegateCallContract {
    function transferSomething(address token, uint256 amount, address recipient)
        public
    {
        IERC20(token).transfer(recipient, amount);
    }
}

MainStateContract.sol

pragma solidity ^0.5.0;

contract MainStateContract {
    address delegateCallContract;

    function setDelegateCallContract(address contractAddress) public {
        delegateCallContract = contractAddress;
    }

    function delegateTransfer(address token, uint256 amount, address recipient)
        public
    {
        string memory sig = "transferSomething(address,uint256,address)";
        address(delegateCallContract).delegatecall(
            abi.encodeWithSignature(sig, token, amount, recipient)
        );
    }
}

SimpleToken.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";

/**
 * @title SimpleToken
 * @dev Very simple ERC20 Token example, where all tokens are pre-assigned to the creator.
 * Note they can later distribute these tokens as they wish using `transfer` and other
 * `ERC20` functions.
 */
contract SimpleToken is ERC20, ERC20Detailed {

    /**
     * @dev Constructor that gives msg.sender all of existing tokens.
     */
    constructor () public ERC20Detailed("SimpleToken", "SIM", 18) {
        _mint(msg.sender, 10000 * (10 ** uint256(decimals())));
    }
}

MainStateContract.test.js

const { accounts, contract } = require('@openzeppelin/test-environment');

const { expect } = require('chai');

// Import utilities from Test Helpers

const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');

// Load compiled artifacts

const DelegateCallContract = contract.fromArtifact('DelegateCallContract');

const MainStateContract = contract.fromArtifact('MainStateContract');

const SimpleToken = contract.fromArtifact('SimpleToken');

// Start test block

describe('MainStateContract', function () {

    const [creator, recipient] = accounts;

    beforeEach(async function () {

        this.delegateCallContract = await DelegateCallContract.new();

        this.mainStateContract = await MainStateContract.new();

        this.simpleToken = await SimpleToken.new({ from: creator });

        await this.mainStateContract.setDelegateCallContract(this.delegateCallContract.address);

        await this.simpleToken.transfer(this.mainStateContract.address, new BN(1000), { from: creator });

    });

    // Test case

    it('delegate', async function () {

        const amount = new BN(100);

        expect(await this.simpleToken.balanceOf(this.mainStateContract.address)).to.be.bignumber.equal(new BN(1000));

        await this.mainStateContract.delegateTransfer(this.simpleToken.address, amount, recipient);

        expect(await this.simpleToken.balanceOf(recipient)).to.be.bignumber.equal(amount);

    });

});

Hi @realisation,

Checking to see how you got on with this?