2

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.

  • I think you should have a look at this answer – Henk Aug 1 '18 at 8:11
  • thanks @henk but events wont solve the problem at hand, I would need to pass param as you notice in P._callback signature. – khawarizmi Aug 1 '18 at 8:17
  • If all callbacks take the same type and amount of parameters, you could just update the handleEvent function to take a parameter and pass it in A. If not, I'll write up another solution – Henk Aug 1 '18 at 8:18
  • In some scenarios contract A may change the parameter to pass to the contracts so events may be a bit unfit for my case – khawarizmi Aug 1 '18 at 8:36
  • Could you please elaborate on that, maybe edit your question? – Henk Aug 1 '18 at 8:37
1

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.

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.