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?
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.