soliditychainlinkchainlink-keepers

Change the state of a variable of a contract B from a Keeper


The purpose is to use this variable from the B contract Im trying with delegate call but doesnt work,only works with event

ContractB.sol

// SPDX-License-Identifier: MIT
pragma solidity >0.8.0;

contract ContractB {
    
    uint256 public tokenName = uint256(2);
    event SetToken(uint256 _tokenName); 

    function setTokenName(uint256 _newName) external returns (uint256) {                
        setInternal(_newName);  
    }
    
    function setInternal (uint256 _newName) public returns (uint256)
    {
        tokenName = _newName;
        emit SetToken(tokenName);
        return tokenName;
    }
            
    function getTokenName() public view returns (uint256)
    {
        return tokenName;
    }        
}

Counter.sol

//Begin
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

interface KeeperCompatibleInterface {
    function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData);
    function performUpkeep(bytes calldata performData) external;
}

contract Counter is KeeperCompatibleInterface {
    
    uint256 public counter;    // Public counter variable

    // Use an interval in seconds and a timestamp to slow execution of Upkeep
    //60 seconds
    uint public immutable interval;
    uint public lastTimeStamp;  //My counter was updated  
    
    //**
    address contractBAddress;
    uint256 public tokenName = uint256(2);
    //**
    
    constructor(uint updateInterval,address _contractBAddress) {
      interval = updateInterval;
      lastTimeStamp = block.timestamp;
      counter = 0;
      contractBAddress=_contractBAddress;
    }

    function checkUpkeep(bytes calldata checkData) external view override returns (bool upkeepNeeded, bytes memory performData) {
        upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
        performData = checkData;
    }

    
    //When checkUpKeep its already to launch, this task is executed
    function performUpkeep(bytes calldata) external override {
        lastTimeStamp = block.timestamp;
        counter=0;
        counter = counter + 1;
        (bool success, bytes memory returndata) = contractBAddress.delegatecall(
              abi.encodeWithSignature("setTokenName(uint256)", counter)
        );

        // if the function call reverted
        if (success == false) {
            // if there is a return reason string
            if (returndata.length > 0) {
                // bubble up any reason for revert
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert("Function call reverted");
            }
        }
    }
    function getTokenName() public view returns (uint256)
    {
        return tokenName;
    }
    
}

The event works perfect, but i cant change the state in ContractB.sol ... https://kovan.etherscan.io/tx/0x7fbacd6fa79d73b3b3233e955c9b95ae83efe2149002d1561c696061f6b1695e#eventlog


Solution

  • The fact that the event worked perfectly is proof that Keeper did its job properly. The problem here is the delegatecall itself.

    When contract A executes delegatecall to contract B, B's code is executed with contract A's storage, msg.sender and msg.value. Storage, current address and balance still refer to the calling contract (contract A), only the code is taken from the called address (contract B).

    In your case, setTokenName updates ContractB's tokenName , ergo ContractB's storage slot 0. The same storage slot at Counter smart contract is uint256 public counter. When you executed delegatecall you updated the Counter’s storage (counter variable) with ContractB’s setTokenName function logic.

    Since you know the ContractB’s ABI why don’t you do something like this?

    pragma solidity ^0.8.0;
    
    import "./ContractB.sol";
    
    contract Counter is KeeperCompatibleInterface {
        ContractB public contractB;
        uint256 public counter;
    
        constructor(ContractB _contractBAddress) {
            contractB = _contractBAddress;
        }
    
        function performUpkeep(bytes calldata) external override {
            counter = counter + 1;
            contractB.setTokenName(counter);
        }
    }