0

Contract A wants to call an external contract B/C/D/E/F but the variable name is different within each contract B/C/D/E/F, and contract A does not know the variable name for B/C/D/E/F at the start.

---Contract A---
function callExternal(address _externalContract, string _variableName) public {
  interface(_externalContract)._variableName
}


---Contract B---
uint256 public variableName;


---Contract C---
uint256 public aDifferentVariableName;

One route is to go through storage slots, but that assumes that each contract will have the same ordering of storage slots. How can I look up specifically the passed in string on another contract.

1

The easiest solution for this would be to create a getter function in the other contracts, that share the same name. Then you can just call that function from contract A to get the variable.

interface Interface {
  function getVariable() external view returns (uint256);
}

contract A {
  function callExternal(address externalContract) public view returns (uint256) {
    return Interface(externalContract).getVariable();
  }
}

contract B is Interface {
  uint256 public foo;
    
  function getVariable() override view external returns (uint256) {
    return foo;
  }
}


contract C is Interface {
  uint256 public bar;
    
  function getVariable() override view external returns (uint256) {
    return bar;
  }
}

Now you can simply call A.callExternal(<address of B/C>); to get the value.

Alternatively, you can use staticcall to get the variables from a string name, since public variables are essentially getters on their own. This requires a bit more extra code though, to get the function selector from a string:

contract A {
  function callExternal(address externalContract, string calldata variableName) public view returns (uint256) {
    (bool success, bytes memory data) = externalContract.staticcall(
      abi.encodeWithSelector(
        bytes4(
          keccak256(
            abi.encodePacked(
              variableName,
              "()"
            )
          )
        )
      )
    );
    
    if (success) {
      return abi.decode(data, (uint256));
    }
  }
}

Now calling A.callExternal(<address of B>, "foo") will return foo from contract B. Note that the variables must be public for this to work.

  • The second solution is the most applicable as my question wasn't clear - the contracts that I wish to get the value from have already been deployed, and are not in my control. In terms of the static call is there a reason why you wrap it inside of: abi.encodeWithSelector(bytes4(keccak256(abi.encodePacked( instead of just wrapping it inside of abi.encodeWithSelector for example – Decc9a881 Sep 3 '20 at 14:59
  • bytes4(keccak256(abi.encodePacked(...))) is necessary to concatenate the strings (basically variableName + "()"). Alternatively you could just use abi.encodeWithSignature(variableName) and specify foo() or bar() as name. – Morten Sep 3 '20 at 15:09
  • Perfect! thanks so much for the assistance Out of curiosity if I was to call a function instead of a variable. I would replace variableName,"()" with functionName,"(_param1, _param2)"? – Decc9a881 Sep 3 '20 at 15:10
  • For functions with parameters you can use abi.encodeWithSignature("<signature>", param1, param2), where the signature is something like function(type1,type2). See the documentation: solidity.readthedocs.io/en/v0.7.1/… – Morten Sep 3 '20 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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