Call()

Published by Mario Oettler on

There are two ways of calling an external function- without ABI and with ABI.

Call Without ABI

The first way is to send a call to an address. This works as follows.

address payable targetAddress = payable(/*address here*/);

targetAddress.call{value: 100, gas: 5000}(abi.encodeWithSignature(“targetFunction(type param 1, type param 2)”, value param 1, value param 2));

In the curly brackets, you can state how much money (in wei) you want to send to the targt contract. You can also say how much gas you are willing to send along. Both value and gas are optional. If neither value nor gas is specified, you need to omit the {}.

Inside the round brackets, you can transport data. Calling a function is done with the abi.encodeWithSignature. You can pass parameters by giving the parameter type (uint, string, bytes, etc.) and the values in the same order.

We need to deploy two contracts. The first contract contains our external function we want to call. And the second contract contains the call.

pragma solidity 0.8.20;

contract targetContract{

    function targetFunction(uint256 _number) public payable returns(uint256 result){
        return _number + 5;
    }
}
pragma solidity 0.8.20;

contract callerContract{
    address payable targetAddress = payable(0x3328358128832A260C76A4141e19E2A943CD4B6D);
    bytes public result;
    
    constructor() payable{
        
    }
    
    function callExternal(uint256 _number) public{
        (bool success, bytes memory returnData) = targetAddress.call{value: 100, gas: 10000}(abi.encodeWithSignature("targetFunction(uint256)",_number));
        result = returnData;
    }    
}

The call statement returns two variables; success, and data. If there was an error (revert, assert) the success is false.

Call With ABI

If you know the ABI, you can perform a call in the following way. The ABI is the interface description of a contract. It means Application Binary Interface. It basically describes what functions and variables there are and what input parameters they expect and what output they send.

targetContract targetC;
    function callExternal(targetContract _address) public returns(uint256){
        targetC = _address;
        uint256 data = targetC.targetFunction{value: 10, gas: 5000}(21);
        return data;
    }

Compared to the call without ABI, you need to know the type of the return value (uint, string, etc.) instead of the generic bytes. You won’t get a success variable. Therefore, you have to take care of the error handling differently.

Try the following code out.

In our example, you need to deploy two contracts, targetContract and callerContract. Submit the address of the targetContract as a function argument when calling the callExternal() function in the callerConract.

pragma solidity 0.8.20;

contract targetContract{
    function targetFunction(uint256 _number) public payable returns(uint256 result){
        return _number + 5;
    }
}

contract callerContract {

    targetContract targetC;
    
    constructor() payable{
        
    }
    
    function callExternal(targetContract _address) public returns(uint256){
        targetC = _address;
        uint256 data = targetC.targetFunction{value: 10, gas: 5000}(21);
        return data;
    }
}

There might be confusion about why we have to compile and deploy the contract that we call. If the contract we want to call (targetContract in our case) is already deployed on the blockchain, it is not necessary to deploy it again. But it is necessary to have it in the same file as the calling contract. The compiler needs it to see if the called function exists and if its parameters are correct.

The called contract operates in its own context. This has two implications:

  • The called contract cannot change or read the storage of the calling contract.

If you have the following code snippet implemented in the calling contract:

uint256 variable1;

uint256 public variable2;

The called contract cannot read variable1. But it could read variable2 by calling its get function.

  • The called contract sees a different msg.sender and msg.value than the calling contract.

For the caller it looks like that:

msg.sender = EOA address

msg.value = EOA send value

But for the called contract it looks like that:

msg.sender = Caller address

msg.sender = Caller send value

Categories:

if()