I'm trying to write unit tests for a Chainlink CCIPSender contract, but as far as I can tell it is reverting when calling getFee()
to pay for the transaction.
In my test I call applyRampUpdates()
from Chainlink CCIP's Router.sol
. I have to specify a destination chain selector
and an onRamp
address. For the address, I have tried msg.sender, the receiver contract and the router itself.
When I run my test, it reverts during the getFee()
call.
This line in Router.getFee()
implies I have to implement my own onRamp
contract with custom getFee()
logic:
return IEVM2AnyOnRamp(onRamp).getFee(destinationChainSelector, message);
Is that correct or am I wrong?
Here is my test:
modifier fundLinkToRevealerAndApprove() {
vm.prank(msg.sender);
link.transfer(REVEALER, STARTING_USER_BALANCE);
vm.deal(REVEALER, STARTING_USER_BALANCE);
vm.prank(REVEALER);
link.approve(address(ccidSender), oraclePayment);
_;
}
modifier authorizedSenders() {
address[] memory authorizedAddresses = new address[](1);
authorizedAddresses[0] = REVEALER;
vm.prank(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f);
operator.setAuthorizedSenders(authorizedAddresses);
_;
}
modifier setDestinationSelectorAndReceiverAddress() {
vm.startPrank(anvilAccount);
ccidSender.setCcidDestinationSelector(ccidDestinationSelector);
ccidSender.setCcidReceiver(ccidReceiver);
vm.stopPrank();
_;
}
modifier applyRampUpdates() {
vm.startPrank(msg.sender);
Router.OnRamp memory newOnRamp = Router.OnRamp({
destChainSelector: 16015286601757825753, // Sepolia
onRamp: ccidReceiver
});
Router.OnRamp[] memory newOnRampArray = new Router.OnRamp[](1);
newOnRampArray[0] = newOnRamp;
router.applyRampUpdates(newOnRampArray, new Router.OffRamp[](0), new Router.OffRamp[](0));
vm.stopPrank();
_;
}
function testFulfillAndSend()
public
fundLinkToRevealerAndApprove
authorizedSenders
setDestinationSelectorAndReceiverAddress
applyRampUpdates
{
vm.startPrank(REVEALER);
ccidSender.requestStatus(REVEALEE);
// bytes32 requestId = ccidSender.getLatestSentRequestId();
bytes32 constantRequestId = 0x5e8ebfa50e778e69264bdc847efd6c474992d0ba91772b41eb52d11737a9eafe;
uint8 kycUserStatus = 1;
uint40 nonZeroKycTimestamp = 1658845449;
bytes memory data = abi.encode(constantRequestId, kycUserStatus, nonZeroKycTimestamp);
operator.fulfillOracleRequest2(
constantRequestId,
oraclePayment,
address(ccidSender),
bytes4(keccak256("fulfill(bytes32,uint8,uint40)")),
block.timestamp + 5 minutes,
data
);
vm.stopPrank();
}
The test function I am testing is testFulfillAndSend()
.
Here are the functions in my Sender contract that it calls:
function fulfill(bytes32 _requestId, Status _status, uint40 _kycTimestamp)
external
override
recordChainlinkFulfillment(_requestId)
{
if (_status == Status.KYCUser) {
if (_kycTimestamp == 0) {
revert CCIDSender__KycTimestampShouldNotBeZeroForKycUser();
}
} else {
if (_kycTimestamp != 0) {
revert CCIDSender__KycTimestampShouldBeZeroForNonKycUser();
}
}
Request storage request = _requests[_requestId];
request.kycTimestamp = _kycTimestamp;
request.isFulfilled = true;
request.isHumanAndUnique = _status != Status.NotFound;
request.isKYCUser = _status == Status.KYCUser;
latestFulfilledRequestId[request.revealee] = _requestId;
emit Fulfilled(_requestId, request.revealer, request.revealee, _status, _kycTimestamp);
sendKycStatusToCcidReceiver(_status);
}
function sendKycStatusToCcidReceiver(Status _status) internal {
string memory statusString = statusToString(_status);
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(ccidReceiver),
data: abi.encode(statusString),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: "",
feeToken: link
});
uint64 destinationChainSelector = ccidDestinationSelector;
uint256 fees = IRouterClient(router).getFee(destinationChainSelector, message);
LinkTokenInterface(link).approve(address(router), fees);
IRouterClient(router).ccipSend(destinationChainSelector, message);
}
Here are the final parts of my test log:
├─ [39126] Operator::fulfillOracleRequest2(0x5e8ebfa50e778e69264bdc847efd6c474992d0ba91772b41eb52d11737a9eafe, 100000000000000000 [1e17], CCIDSender: [0x5FbDB2315678afecb367f032d93F642f64180aa3], 0xccd8682400000000000000000000000000000000000000000000000000000000, 301, 0x5e8ebfa50e778e69264bdc847efd6c474992d0ba91772b41eb52d11737a9eafe00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000062dff909)
│ ├─ emit OracleResponse(requestId: 0x5e8ebfa50e778e69264bdc847efd6c474992d0ba91772b41eb52d11737a9eafe)
│ ├─ [33555] CCIDSender::fulfill(0x5e8ebfa50e778e69264bdc847efd6c474992d0ba91772b41eb52d11737a9eafe, 1, 1658845449 [1.658e9])
│ │ ├─ emit ChainlinkFulfilled(id: 0x5e8ebfa50e778e69264bdc847efd6c474992d0ba91772b41eb52d11737a9eafe)
│ │ ├─ emit Fulfilled(_requestId: 0x5e8ebfa50e778e69264bdc847efd6c474992d0ba91772b41eb52d11737a9eafe, _revealer: REVEALER: [0x513dcA22893fBB7eB2C737A3b3D046d03D8371AD], _revealee: REVEALEE: [0x50FA752360aD481DDbE824f39Cbc32FA9FD9458d], _status: 1, _kycTimestamp: 1658845449 [1.658e9])
│ │ ├─ [6860] Router::getFee(16015286601757825753 [1.601e19], EVM2AnyMessage { receiver: 0x000000000000000000000000c32c3d415b8597604342351d7246f85e29ad6efb, data: 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000084b59435f55534552000000000000000000000000000000000000000000000000, tokenAmounts: [], feeToken: 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76, extraArgs: 0x }) [staticcall]
│ │ │ ├─ [0] ccidReceiver::getFee(16015286601757825753 [1.601e19], EVM2AnyMessage { receiver: 0x000000000000000000000000c32c3d415b8597604342351d7246f85e29ad6efb, data: 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000084b59435f55534552000000000000000000000000000000000000000000000000, tokenAmounts: [], feeToken: 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76, extraArgs: 0x }) [staticcall]
│ │ │ │ └─ ← ()
│ │ │ └─ ← "EvmError: Revert"
│ │ └─ ← "EvmError: Revert"
│ └─ ← false
├─ [0] VM::stopPrank()
│ └─ ← ()
└─ ← ()
If anyone could please offer any advice as to what I should be doing, I would be very grateful. To summarize: I am trying to test a CCIP Sender contract that appears to be reverting when calling getFee()
and I am unclear about what address should be the onRamp
, and the reasons for all of this. Thank you.