5

My code

for ( uint256 index = 0; index < 5; index++) {
              string memory name = GetName_v1();
            }

GetName_v1 is a method name and it should be actually a dynamic method name like

GeName_v${index}

Is there a way in solidity to achieve such? Or what is the proper way of doing it?

3 Answers 3

3

I think that Mikko's way is the only way even if you want to call other methods of the same contract.

pragma solidity ^0.8.4;


contract Test {
    uint value;
    
    function set(uint n) external{
        value = n;
    }
    
    function get() external view returns(uint) {
        return value;
    }
    
    function callByName(string calldata functionSignature, bytes calldata parameters) external returns(bytes memory) {
        (bool success, bytes memory data) = address(this).delegatecall(
            abi.encodeWithSelector(
                bytes4(
                    keccak256(bytes(functionSignature))
                ),
                parameters
            )
        );
        require(success, 'Call failed');
        return data;
    }
}

Here for example you can call get() dynamically calling callByName('get()', '0x') instead. Note that the functionSignature parameter must include the paramters types e.g. for set() it should be set(uint256). Using this method you must provide the parameters encoding and return data decoding.

I used delegatecall since in this case it should be equivalent to call but I have seen that this way it needs slightly less gas.

However this solution isn't great for at least two reasons:

  • All the functions you want to call dynamically must be public or external, you could make a bad work around adding a modifier like this:
modifier isPrivate() {
    require(msg.sender == address(this), 'This method is private');
    _;
}
  • The compiler manages really differently externally callable methods (public/external) from internally callable (private/internal) and if you want to use this "trick" you must declare your methods externally callable. Internally callable methods are managed using the JUMP opcode, indeed my first idea was to write an inline assembly snippet to simply jump but unfortunately this is not possible using YUL. For this reason calling methods in this way will make your contract spend more gas since it needs an additional internal transaction.
4
  • won't using delegatecall() produce potentially weird effects from using memory where you don't expect it, and not referencing the memory of the contract you're calling when you do expect it...?
    – Kyle Baker
    Commented Oct 28, 2021 at 22:00
  • My knowledge about this topic is limited, however I cannot understand what you mean, afaik a delegatecall is like a normal call with the difference that the executed code uses the caller's storage. For the uses I've made of it I never noticed any weirdness with the memory. But I repeat that I might sayng inaccuracies, that snippet was only a quick and cool proof of concept I wanted to try, I never used it for a real project. Commented Oct 29, 2021 at 12:11
  • I'd say be careful, delegatecall() makes sense with a proxy format, but you shouldn't use this pattern for calling any given arbitrary contract without looking carefully into exactly how delegatecall() works.
    – Kyle Baker
    Commented Oct 29, 2021 at 13:27
  • > If state variables are accessed via a low-level delegatecall, the storage layout of the two contracts must align in order for the called contract to correctly access the storage variables of the calling contract by name. This is of course not the case if storage pointers are passed as function arguments as in the case for the high-level libraries. --see warnings here: docs.soliditylang.org/en/v0.8.9/…
    – Kyle Baker
    Commented Oct 30, 2021 at 12:43
1

Yes. You can manually construct calldata which contains the input function selector (4 bytes) and then arguments to the function.

Here is a post that covers the topic.

6
  • Thanks but that is not actually the solution I am looking for. Infact the dynamic methods all exists within the same contract.
    – NinjaMAN
    Commented Jul 15, 2021 at 9:02
  • AFAIK you can call your own contract in similar manner. Commented Jul 15, 2021 at 9:48
  • But I dont see function name being dynamic there. Because Test is the function name and its static in the code as bytes memory payload = abi.encodeWithSignature("Test(uint, uint)", 12, 35); (bool success, bytes memory returnData) = address(addr).call(payload); require(success);
    – NinjaMAN
    Commented Jul 15, 2021 at 10:47
  • You can generate strings dynamically in Solidity. How to do this please read Solidity documentation or open another question, as it is out of scope. Commented Jul 15, 2021 at 10:48
  • my intention is not to generate strings dynamically. I already have 10 method names as method1,method2 .... method10. The only dynamic in method name is the integer appended at last i.e method${integer}
    – NinjaMAN
    Commented Jul 15, 2021 at 11:01
0

This is playing with fire, but it can be done:

    function execute(address _to, bytes calldata _data)
        public
        payable
        returns (bytes memory)
    {
        (bool success, bytes memory returnData) = _to.call{value: msg.value}(_data);
        require(success);
        return returnData;
    }

Note that "success" returns true any time there is no error... but there is no checking for whether the end address is a contract, or whether the function you are trying to call exists, etc.

Also, you shouldn't change any state after doing this call. You're asking for reentrancy hacks.

How to generate that _data param? Here's how you'd do it in a default hardhat test environment (using ethers js):

  function asExecuteArg(functionName, argTypes, ...args) {  
    let signature = `function ${functionName}(${argTypes})`
    let ABI = [signature];
    let interface = new ethers.utils.Interface(ABI);
    let output = interface.encodeFunctionData(functionName, args);
    return output;
  }

  const AccountForwarder = await ethers.getContractFactory("AccountForwarder");
  contractInstance = await AccountForwarder.deploy();

  const executeArg = asExecuteArg('existingFunction', "uint256", 12);

  await contractInstance.execute(contractAddress, executeArg);

contractAddress will be the address of the deployed contract that has existingFunction(uint256 _someArg) in it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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