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);
});
});