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()
│ └─ ← ()
└─ ← ()
Whatever address I specify as the onRamp
in my test's applyRampUpdates
modifier is the address that getFee()
attempts to be called from after the Router, so in that test log you can see the ccidReceiver
address is getting called.
I have to call applyRampUpdates()
on the Router in my test because I need to specify a destinationChainSelector
.
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.