Solidity – How to Implement Contracts Callback in Solidity

contract-designcontract-developmentsolidity

I have two contracts, an active contract A is interacting with the external world and many passive contracts P (dynamically added) are designed to trigger some functions when in contract A certain conditions are met.

Therefore contract A is supposed to call a callback function of each of P contracts; I was wondering what would be the best way to register a callback function of newly added P into already deployed contract A?

contract A{

   uint numberOfContractsAdded;

   struct ToCallBackContracts{
     address contractAddress;
     bytes4 contractMethodToCall;
     bytes32 theOnlyArgument;
   }

   mapping(uint -> ToCallBackContracts) contracts;


   constructor A(){}

   function triggeredFromOutside(bytes32 param)public returns (bool){
     for(var i=0; i<numberOfContractsAdded; i++){
       //2- how? call the callback function of each registered contract
     }
   }

   function registerCallback(bytes4 _cMethod, address _cAddress){
      //1- how? to save contract/method, number of parameters to receive?
   }
}


contract P{
    address addressOfA;
    constructor P(address _addressOfA){
        addressOfA = _addressOfA;
    }

    function _callback(bytes32 param){
       //do something
    }

    function callFromOutsideToRegisterWithA(bytes32 param){
       //3- how? to register with A   
    }

}

There are 3 HOWs pointed out in the code for better understanding to summarize the question if I understand it right..

disclaimer: code is not tested/compiled/checked for syntax and only for demonstration purposes.

Best Answer

This is what I came up with by filling in the blanks and edidting your code as little as possible. The difference with using events in your case, is that for each callback, a different value for param can be provided.

I'm certainly not very fond of this architure, as I think you should use interfaces instead, but alas.

pragma solidity 0.4.24;

contract A{

    struct ToCallBackContract {
        address contractAddress;
        bytes4 contractMethodToCall;
        bytes32 theOnlyArgument;
    }

    ToCallBackContract[] public contracts;

    // This mapping is used for finding contracts by their address
    mapping(address => uint256) contractIndexes;

    function triggeredFromOutside() public returns (bool){
        // Don't use var, the compiler wil infer you use uint8, and it might overflow quickly
        for(uint256 i=0; i< contracts.length; i++){
            //2- how? call the callback function of each registered contract

            ToCallBackContract storage toCallBackContract = contracts[i];
            // THIS MIGHT BE UNSAFE, PLEASE BE CAREFUL
            address(toCallBackContract.contractAddress).call(toCallBackContract.contractMethodToCall, toCallBackContract.theOnlyArgument);
        }

    }

    function registerCallback(bytes4 _cMethod, address _cAddress) public {
        //1- how? to save contract/method, number of parameters to receive?

        // Incrementing the lenght of the array creates an empty struct.
        uint256 index = contracts.length++;

        // Fill it up
        contracts[index].contractAddress = _cAddress;
        contracts[index].contractMethodToCall = _cMethod;

        // Register it in the indexes mapping
        contractIndexes[_cAddress] = index;
    }

    // This function sets the argument
    function setTheOnlyArgument(address _contractAddress, bytes32 _value) public {
        ToCallBackContract storage toCallBackContract = contracts[contractIndexes[_contractAddress]];
        toCallBackContract.theOnlyArgument = _value;
    }
}


contract P{
    A addressOfA;
    constructor(A _addressOfA) public {
        addressOfA = _addressOfA;
    }

    function _callback(bytes32 param) public {
       //do something
       emit SomethingDone(param);
    }

    function callFromOutsideToRegisterWithA() public {
       //3- how? to register with A 

       // A function signature is calculated like so:
       bytes4 sig = bytes4(keccak256("_callback(bytes32)"));
       // Then, register it
       addressOfA.registerCallback(sig, this);
    }

    event SomethingDone(bytes32 param);
}

To use it, first deploy A. The, deploy a P using the address of the deployed A. After that, call P's callFromOutsideToRegisterWithA method. This registers P and its _callback method with A. To set the value of the argument that is going to be sent to the _callback method of P, use A's setTheOnlyArgument method providing the address of P and the value you want A to pass to it.

Then, call A's triggeredFromOutside method, which whill call each registered P's callback, passing along the given value for theOnlyArgument.

Related Topic