Search code examples
chainlink

My chainlink request isn't getting fulfilled?


Can someone help me investigate why my Chainlink requests aren't getting fulfilled. They get fulfilled in my tests (see hardhat test etherscan events(https://kovan.etherscan.io/address/0x8Ae71A5a6c73dc87e0B9Da426c1b3B145a6F0d12#events). But they don't get fulfilled when I make them from my react app (see react app contract's etherscan events https://kovan.etherscan.io/address/0x6da2256a13fd36a884eb14185e756e89ffa695f8#events).

Same contracts (different addresses), same function call.

Updates:

Here's the code I use to call them in my tests

        const tx = await baseAgreement.connect(user).withdraw(
            jobId,
            oracleFee
        );

Here's the code I use to call them in my UI

    const signer = provider.getSigner();
    const tx = await baseAgreement.connect(signer).withdraw(jobId, oracleFee);

Here's my Solidity Chainlink functions


    function withdraw(
        bytes32 _jobId,
        uint256 _oracleFee
    )
        external
        onlyContractActive()
        returns(bytes32 requestId)
    {
        // check Link in this contract to see if we need to request more
        checkLINK(_oracleFee);

        // Build request
        Chainlink.Request memory req = buildChainlinkRequest(_jobId, address(this), this.fulfillWithdraw.selector);
        bytes memory url_bytes = abi.encodePacked(BASE_URL, mediaLink, API_KEY);
        req.add("get", string(url_bytes));


        req.add("path", "items.0.statistics.viewCount");
        return sendChainlinkRequestTo(chainlinkOracleAddress(), req, _oracleFee);
    }

    /**
     * @dev Callback for chainlink, this function pays the user
     */
    function fulfillWithdraw(
        bytes32 _requestId,
        bytes32 _response
    )
        external
        recordChainlinkFulfillment(_requestId)
    {
        // Convert api string response to an int
        string memory _responseString = bytes32ToString(_response);
        uint256 response = uint256(parseInt(_responseString, 0));
        emit IntResponse(response);
        // Pay the user
        payUser(response);
    }

    function payUser(
        uint256 _number
    )
        internal
    {
        // Calculate pay
        uint256 budgetRemaining = getAgreementBalance();
        uint256 accumulatedPay = budget - budgetRemaining;
        uint256 pay = (payPerNumber * _number) - accumulatedPay;
        if (pay > budgetRemaining) {
            pay = budgetRemaining;
        }
        // Calculate platform fee
        uint256 totalPlatformFee = (pay * PLATFORM_FEE) / 100;
        // Transfer funds
        paySomeone(payable(address(this)), user, pay-totalPlatformFee);
        paySomeone(payable(address(this)), platformAddress, totalPlatformFee);
    }

Full contract code can be viewed here: https://github.com/colinsteidtmann/dapplu-contracts/blob/main/contracts/BaseAgreement.sol

Update 2:

I figured out that my UI was deploying my contracts using a factory contract and a clones pattern (based on EIP 1167 standard and OpenZepplin's clones https://docs.openzeppelin.com/contracts/4.x/api/proxy#Clones ). But, my hardhat tests were deploying my contracts without the factory. Once I made my hardhat tests deploy the contracts using the factory contract, then they stopped working. So, does chainlink not work with Proxy contracts and the EIP 1167 standard?


Solution

  • Remove your agreement vars in MinimalClone.sol, and either have the user input them as args in your init() method or hardcode them into the request like this:

    Chainlink.Request memory req = buildChainlinkRequest(_jobId, address(this), this.fulfillWithdraw.selector);
    req.add("get", "https://youtube.googleapis.com/youtube/v3/videos?part=statistics&id=aaaaaakey=aaaaa");
    

    The reason it wasn't working is that proxy contracts do not inherit the state of the implementation contracts, just the logic through the delegatecall() method. Thus, your proxy clones were reading essentially blank values when replacing those variables in the request.

    Reference: Here is a good article on how proxies and delegate call works.